Skip to content

fix(spec): catch nixpkgs-reference pin drift, and repair the drift #55 introduced - #56

Merged
fredclausen merged 4 commits into
mainfrom
chore/spec-pin-guard
Sep 18, 2026
Merged

fredclausen merged 4 commits into
mainfrom
chore/spec-pin-guard

Conversation

@fredclausen

@fredclausen fredclausen commented Sep 18, 2026

Copy link
Copy Markdown
Member

Follow-up to #55. Closes the hole that let the spec-corpus oracle pin drift
silently, and repairs the drift #55 introduced.

Supersedes #45.

The bug

tests/spec/REFERENCE.md declared nixpkgs_rev = "aec71e3…" while flake.nix
pinned 88ae382…. They diverged in #55, which merged the Renovate digest bump
for nixpkgs-reference without the REFERENCE.md update that the file's own
upgrade policy demands:

Bumps are intentional. Edit the [reference] block in this file and the
nixpkgs-reference.url rev in flake.nix in the same commit.

That was my miss in #55 — the roundup treated #45 as a routine digest bump when
the input it touches is explicitly policy-gated.

Why nothing caught it

Two checks existed and both missed it by construction:

  • cargo xtask spec versions compared the pinned bash/coreutils versions
    against the devshell, never the rev.
  • on_disk_reference_doc_parses compared REFERENCE.md against a hardcoded
    constant
    , never against flake.nix.

So the doc and the test agreed with each other while both disagreed with the
flake, and spec versions printed pin matches devshell: ok.

The version-only check cannot detect this even in principle: aec71e3,
88ae382 and ad903c1 all resolve to bash 5.3p15 / coreutils 9.11. Different
revs, identical toolchain. The rev is the only field that distinguishes them.

Changes

1. fix(spec): record the rev flake.nix actually pinsREFERENCE.md
aec71e388ae382, pinned_on → the date it was adopted on main.

2. fix(spec): verify the rev, not just the versions — adds
parse_flake_input_rev, which reads <input>.url from flake.nix and requires
a full 40-char object name; a branch ref or short digest yields NotPinned,
since an unpinned oracle is the failure the pin exists to prevent. The rev is now
compared in run_versions and in a test that reads both files from disk.

run_versions crossed clippy::too_many_lines (103/100), so the flake read and
the comparison were extracted into read_flake_rev and verify_pin rather than
suppressed. verify_pin being a free function also made the comparison directly
unit-testable.

3. ci: run cargo xtask spec versions in the compat job — the rev half is
covered by cargo test --all, but the version half needs the
FREDSHELL_REFERENCE_* vars only the devshell exports, so it can't run in the
bare-runner matrix. Ordered before the compat report: if the oracle isn't the one
the fixtures were recorded against, the pass rate measures the wrong bash.

4. chore(renovate): disable the bot for nixpkgs-reference — its PRs are
unmergeable as written, since the policy needs REFERENCE.md + fixture work
Renovate can't do. #45 sat open 2.5 weeks proving it.

Correction to what I said earlier: I claimed this input was auto-merging and
that this defeated the pin. That was wrong. The existing rule matches
major/minor/patch/pin, and these arrive as digest updates, so they fell
through to the top-level automerge: false#45's body confirms "Automerge:
Disabled by config". The real problem is noise plus the standing invitation to
merge without the policy work, which is what happened in #55.

Fixtures

Unaffected, and verified rather than assumed. All three revs give bash 5.3p15 /
coreutils 9.11, and cargo xtask compat reports regression: 0 against the
55-case corpus, so the recorded sidecars still match the pinned reference bash.

Worth noting: REFERENCE.md policy item 2 tells you to run
cargo xtask spec record --all, but no --all flag exists — record takes a
single <CASE>. Left alone here; flagging it as a real doc/impl mismatch.

Verification

The guard was confirmed to actually fire, not just to pass. Re-pointing
REFERENCE.md at the old rev:

error: nixpkgs_rev: REFERENCE.md = aec71e3…, flake.nix = 88ae382…
spec versions: REFERENCE.md disagrees with flake.nix or the nix devshell. …
exit=1

and on_disk_reference_doc_rev_matches_the_flake fails with both revs named.

Check Result
cargo xtask check (stable) exit 0
cargo xtask check (nightly 1.100.0) exit 0
cargo xtask spec versions exit 0
pre-commit run --all-files exit 0
cargo test -p xtask 92 passed

9 new tests: 5 for parse_flake_input_rev (dotted form, sibling-input
confusion, unpinned branch ref, short digest, missing input), 3 for verify_pin
(consistent, stale-rev-with-agreeing-versions, version mismatch), 1 on-disk
consistency test.

Summary by CodeRabbit

  • Bug Fixes

    • Compatibility checks now verify the pinned toolchain revision in addition to Bash and coreutils versions.
    • Reports now identify all detected version or revision mismatches together, making compatibility issues easier to diagnose.
    • Added validation for missing, malformed, stale, or inconsistent toolchain references.
  • Chores

    • Updated the pinned reference toolchain revision used for compatibility verification.
    • Automated dependency updates are disabled for the reference toolchain, which now requires coordinated manual updates.

REFERENCE.md declared nixpkgs_rev = aec71e3 while flake.nix pinned
88ae382. The two diverged in #55, which merged the Renovate digest
bump for nixpkgs-reference without the accompanying REFERENCE.md
update that this file's own upgrade policy requires ("Edit the
[reference] block in this file _and_ the nixpkgs-reference.url rev
in flake.nix in the same commit").

Nothing caught it. `cargo xtask spec versions` only compares the
pinned bash/coreutils *versions* against the devshell, and
on_disk_reference_doc_parses only compares REFERENCE.md against a
hardcoded constant — so the doc and the test agreed with each other
while both disagreed with the flake. The following commit closes
that hole.

No fixtures are affected: aec71e3, 88ae382, and ad903c1 all resolve
to bash 5.3p15 and coreutils 9.11, so the oracle never actually
changed. `cargo xtask compat` reports regression: 0 against the
corpus, confirming the recorded sidecars still match the pinned
reference bash.

pinned_on is set to the date 88ae382 was adopted on main (#55),
not the date the rev was authored upstream.
`cargo xtask spec versions` compared the pinned bash/coreutils
versions against the devshell but never checked the rev, and
on_disk_reference_doc_parses compared REFERENCE.md against a
hardcoded constant rather than against the flake. The doc and the
test therefore agreed with each other while both disagreed with
flake.nix, which is how the pin silently drifted in #55.

Two different nixpkgs revs can ship identical bash and coreutils —
aec71e3, 88ae382 and ad903c1 all give bash 5.3p15 / coreutils 9.11
— so a version-only comparison reports "ok" on a stale pin by
construction. The rev is the only field that distinguishes them.

Adds `parse_flake_input_rev`, which reads `<input>.url` out of
flake.nix and requires a full 40-character object name. A branch
ref or a short digest yields `NotPinned` rather than being
accepted, since an unpinned reference input is precisely the
failure the pin exists to prevent.

The rev is now compared in `run_versions` (checked first, as the
root cause when versions coincide) and in a new test that reads
both files from disk. Verified the guard actually fires: re-pointing
REFERENCE.md at the old rev fails the test and exits 1 from
`spec versions` with a message naming both revs and the policy.

Because the new test lives in `cargo test --all`, it is already
gated by the existing `xtask check` matrix and the pre-commit hook —
this class of drift can no longer reach main.

`run_versions` crossed clippy::too_many_lines (103/100) with the
added check, so the flake read and the comparison are extracted into
`read_flake_rev` and `verify_pin` rather than suppressing the lint.
`verify_pin` being a free function also makes the comparison directly
unit-testable, including the #55 case where the rev is stale but both
versions still agree.
Adds a fredshell spec versions
======================

Pinned in tests/spec/REFERENCE.md:
  bash       : 5.3p15
  coreutils  : 9.11
  rev        : 88ae3822eb8aec31f12a4a1895cb064413511177
  pinned on  : 2026-09-18

Resolved from `nixpkgs-reference` (nix devshell):
  bash       : 5.3p15
  coreutils  : 9.11
  rev        : 88ae3822eb8aec31f12a4a1895cb064413511177

pin matches flake.nix and devshell: ok

Floating nixos-unstable (advisory):
  bash       : 5.3p15
  coreutils  : 9.11
  no drift step to the Spec compat report
job, so a REFERENCE.md / flake.nix / devshell disagreement fails CI
rather than being reported as "ok".

The rev comparison is already covered by a unit test in cargo test
--all, but the version half of the check reads the
FREDSHELL_REFERENCE_* vars that only the nix devshell exports, so
it cannot run in the bare-runner check matrix. The compat job
already runs inside nix develop, which makes it the only place this
can live.

Ordered before the compat report deliberately: if the resolved
oracle is not the one the fixtures were recorded against, the pass
rate is measuring the wrong bash and is worse than no number at
all.
nixpkgs-reference is the pinned oracle for the bash-compat spec
corpus. Its upgrade policy in tests/spec/REFERENCE.md requires the
[reference] block and the flake.nix rev to move in the same commit,
re-recording any affected fixtures. Renovate can do neither, so its
PRs for this input are unmergeable as written — #45 sat open for
two and a half weeks proving the point, and #55 merged an earlier
incarnation of it without the REFERENCE.md half, which is how the
pin drifted in the first place.

Note this input was never auto-merged: the existing rule matches
major/minor/patch/pin, and these arrive as digest updates, so they
fell through to the top-level automerge: false. The problem is
noise and the invitation to merge without the policy work, not
silent automerging.

Drift versus nixos-unstable is still surfaced advisorily by
cargo xtask spec versions, so disabling the bot loses no signal.
Bump the pin by hand, deliberately.
Copilot AI lite review requested due to automatic review settings September 18, 2026 18:30
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: c3ff5beb-50b7-4653-a1cb-5bbf2b8e83ae

📥 Commits

Reviewing files that changed from the base of the PR and between a50fad0 and 3f10aea.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • renovate.json
  • tests/spec/REFERENCE.md
  • xtask/src/spec/mod.rs

Included review availability: 9 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.


📝 Walkthrough

Walkthrough

The specification tooling now parses the pinned Nix revision from flake.nix and verifies it against the reference document and tool versions. The reference pin was updated, Renovate updates were disabled, and CI now runs the verification before the compatibility report.

Changes

Toolchain Pin Validation

Layer / File(s) Summary
Flake parsing and pin verification
xtask/src/spec/mod.rs
Adds flake revision parsing, validates exact 40-character hexadecimal revisions, compares the revision with Bash and coreutils pins, reports all mismatches, and adds regression tests.
Reference pin and update policy
tests/spec/REFERENCE.md, renovate.json
Updates the recorded Nix revision and pin date. Renovate no longer updates nixpkgs-reference automatically.
Compatibility job enforcement
.github/workflows/ci.yml
Runs cargo xtask spec versions in the Nix development shell before the compatibility report.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant CI compat job
  participant run_versions
  participant flake.nix
  participant verify_pin
  CI compat job->>run_versions: run cargo xtask spec versions
  run_versions->>flake.nix: read nixpkgs-reference revision
  flake.nix-->>run_versions: return pinned revision
  run_versions->>verify_pin: compare revision and tool versions
  verify_pin-->>CI compat job: return validation result
Loading

Merge Risk: ⚪ Minimal · up to 3f10a

The reference revision, validation tooling, and CI enforcement align without an identified merge-blocking issue.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: detecting and correcting nixpkgs-reference pin drift. It is specific and consistent with the pull request objectives.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 18 functions across 1 files. (3 skipped: 3 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Address the updater-workflow exclusion and tighten .url attribute matching.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR prevents drift between the documented spec-oracle revision and the flake.nix pin, adds CI verification, and disables unsupported Renovate updates.

Changes:

  • Added flake revision parsing and consistency checks.
  • Corrected REFERENCE.md.
  • Added CI validation and Renovate exclusion.
File summaries
File Summary
xtask/src/spec/mod.rs Adds revision parsing and pin verification. Moderate issue remains in attribute matching.
tests/spec/REFERENCE.md Records the corrected oracle revision and date.
renovate.json Disables Renovate updates for the oracle input; a critical updater-workflow gap remains.
.github/workflows/ci.yml Runs pin verification before compatibility reporting.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread renovate.json
Comment on lines +31 to +34
"description": "The nixpkgs-reference input is the pinned oracle for the bash-compat spec corpus (PLAN_05 4.5). Its upgrade policy in tests/spec/REFERENCE.md requires the [reference] block and the flake.nix rev to change in the same commit, re-recording any affected fixtures. Renovate cannot do either, so a bot PR here is unmergeable as-is and only adds noise; #45 sat open for weeks for exactly this reason. Version drift versus nixos-unstable is already surfaced advisorily by `cargo xtask spec versions`, so nothing is lost by disabling it. Bump this pin by hand.",
"matchManagers": ["nix"],
"matchDepNames": ["nixpkgs-reference"],
"enabled": false
Comment thread xtask/src/spec/mod.rs
Comment on lines +162 to +164
if !trimmed.starts_with(&needle) {
continue;
}
@fredclausen
fredclausen merged commit 6f6b105 into main Sep 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants