Skip to content

Commit 9f96e2c

Browse files
authored
fix(quarto-citeproc): stop false CSL lockfile mismatches (#380)
1 parent 25345d5 commit 9f96e2c

4 files changed

Lines changed: 419 additions & 255 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# CSL manifest lockfile false-positive on Windows incremental builds
2+
3+
Strand: bd-2w80 ("Investigate CSL manifest test failure after rebase") — **closed**.
4+
5+
## Resolution (final)
6+
7+
Neither research agent's mechanism theory panned out. Empirical repro (add
8+
then remove a fixture file, no `cargo clean`) showed directory-level
9+
`rerun-if-changed` correctly triggers a rebuild both ways on cargo
10+
1.97.0-nightly — the "directory watch misses changes" theories (both the
11+
pre-1.50-citation one and the mtime-granularity one) were never confirmed as
12+
the actual cause. Bigger finding: the baked `expected` string only ever
13+
depended on file names/counts, never content — so the per-file
14+
`rerun-if-changed` fix originally planned below **would not have prevented
15+
this failure even if applied**, since it only helps with content-edit
16+
detection.
17+
18+
Actual fix shipped: stopped baking the validation state at build time
19+
entirely. `csl_validate_manifest` is now a hand-written runtime test in
20+
`tests/integration/csl_conformance.rs` that reads `test-data/csl-suite/` +
21+
`tests/enabled_tests.txt` live at test-run time (via
22+
`env!("CARGO_MANIFEST_DIR")`, a compile-time *path* constant, not a baked
23+
*computed value*) and compares against `tests/csl_conformance.lock` in the
24+
same instant. No time-of-check/time-of-use gap, no dependency on
25+
build-script rerun timing for this check at all. `build.rs` now only handles
26+
per-fixture test codegen (unaffected — verified byte-identical by an
27+
independent review pass). Full crate suite green: 863 passed, 0 failed.
28+
29+
The per-file `rerun-if-changed` hardening for `quarto-citeproc` +
30+
`quarto-sass` (checklist items below) was **not applied** — no reproducible
31+
defect was ever found for that angle, so it would have been unproven
32+
defensive code.
33+
34+
---
35+
36+
## Original investigation (kept for history)
37+
38+
## Overview
39+
40+
`quarto-citeproc::csl_conformance::csl_validate_manifest` failed on a Windows
41+
incremental build with "Lockfile mismatch: tests/csl_conformance.lock", even
42+
though `git status` showed `tests/enabled_tests.txt` and `test-data/csl-suite/`
43+
clean, and regenerating the lockfile (`UPDATE_CSL_LOCKFILE=1`) produced a
44+
byte-identical file. `cargo clean -p quarto-citeproc` + fresh build made the
45+
test pass with zero lockfile changes. Conclusion: `build.rs` served a stale
46+
baked `expected` value from an old `generated_csl_tests.rs`, not a real data
47+
divergence.
48+
49+
Two research passes (Opus, repo-specific; Sonnet, ecosystem-wide) looked at
50+
*why* `build.rs`'s `cargo:rerun-if-changed=test-data/csl-suite` (a directory,
51+
not per-file) failed to trigger a rebuild, and disagree on mechanism:
52+
53+
- **Opus**: cited [rust-lang/cargo#2599](https://github.com/rust-lang/cargo/issues/2599)
54+
— directory `rerun-if-changed` only tracks the directory's own mtime, misses
55+
in-place file edits.
56+
- **Sonnet**: found #2599 was *fixed* by
57+
[rust-lang/cargo#8973](https://github.com/rust-lang/cargo/pull/8973), merged
58+
2020-12-14, shipped in **Cargo 1.50**. Since then, directory
59+
`rerun-if-changed` recursively scans and takes the max mtime of every file
60+
inside. Current repo toolchain is `nightly-2026-04-28` → `cargo
61+
1.97.0-nightly` — Opus's cited mechanism is stale for the cargo version we
62+
actually run.
63+
64+
So the "directory watch is shallow" story is **not confirmed** for our cargo
65+
version. Sonnet's alternate candidates: mtime-granularity/timestamp-truncation
66+
bugs Cargo has open elsewhere (e.g.
67+
[#13119](https://github.com/rust-lang/cargo/issues/13119), WSL/9p nanosecond
68+
truncation; [#9445](https://github.com/rust-lang/cargo/issues/9445), relative
69+
path normalization in dep-info) are more plausible, but neither is a confirmed
70+
match either — they're precedent for "Cargo's rerun-if-changed freshness check
71+
is fragile on Windows-adjacent filesystems in general," not a proven cause
72+
here.
73+
74+
**Before writing any public rationale (tracker comment, commit message), we
75+
need to settle the actual mechanism empirically on this machine** — not ship a
76+
fix with a plausible-sounding but unverified "why."
77+
78+
The concrete fix both agents converge on regardless of mechanism — enumerate
79+
every fixture file explicitly for `rerun-if-changed` instead of relying solely
80+
on the directory line — is correct either way: it's strictly more precise than
81+
Cargo's own directory scan, and matches an existing in-repo pattern
82+
(`watch_recursive()` in `quarto-trace-server`, `quarto-preview`,
83+
`quarto-mcp-launcher` build.rs files). `quarto-sass/build.rs` has the same
84+
directory-only gap (`resources/scss`, `src`) and has not yet manifested a
85+
failure — worth fixing in the same pass since it's the same category of bug.
86+
87+
## Checklist
88+
89+
- [ ] **Reproduce mechanism empirically.** With current `generated_csl_tests.rs`
90+
freshly built (no dirty cache), touch *only the mtime and content* of one
91+
existing file in `test-data/csl-suite/` (no add/remove), then run
92+
`cargo nextest run -p quarto-citeproc csl_validate_manifest` without
93+
`cargo clean` first. If build.rs re-runs and the test still passes (no
94+
new fixture added, so `expected` shouldn't change anyway) — need a
95+
variant that actually changes what's baked, e.g. add a fixture to
96+
`test-data/csl-suite/` *and* to `enabled_tests.txt`, then only touch
97+
mtime-adjacent files to see if a plain incremental build (no clean)
98+
picks up the addition. Confirm whether Cargo actually re-invokes
99+
build.rs (check via `cargo build -vv -p quarto-citeproc | grep
100+
"Running.*build-script"` or by adding a temporary `eprintln!` in
101+
build.rs) or serves stale output.
102+
- [ ] **Identify actual trigger** for the original failure if possible — was it
103+
a `git rebase`/checkout that touched files at/before Cargo's cached
104+
build-script timestamp? Check `git reflog` / recent branch history if
105+
still reconstructable; otherwise document as "not reproduced from a
106+
clean baseline, only observed once" and rely on the mechanism test above.
107+
- [ ] **Apply per-file `rerun-if-changed` fix** to `crates/quarto-citeproc/build.rs`:
108+
after `test_files` is collected and sorted (existing code, ~line 36),
109+
loop over it and emit `cargo:rerun-if-changed=<path>` per fixture. Keep
110+
the existing directory line (still needed/harmless for catching
111+
additions/removals). No new `walkdir` dependency needed — `csl-suite/`
112+
is flat, and the file list is already collected.
113+
- [ ] **Apply the same fix to `crates/quarto-sass/build.rs`** (`resources/scss`
114+
and `src` directory watches) — same latent gap, not yet triggered.
115+
Confirm whether that build.rs already collects a file list to reuse, or
116+
needs its own walk (check if `walkdir` is already a build-dependency
117+
there for the sibling crates' pattern).
118+
- [ ] **Verify per project TDD/bug-fix rules**: this is build-script infra, not
119+
app logic, so the "test" is the reproduction procedure itself (already
120+
have one: `cargo clean -p quarto-citeproc` fixes it; need the
121+
no-clean-incremental repro from step 1 to prove the *fix*, not just the
122+
symptom, is addressed). Confirm fixed build.rs reruns correctly on a
123+
fixture add without requiring `cargo clean`.
124+
- [ ] **Full workspace verify**: `cargo build --workspace`, `cargo nextest run
125+
--workspace`, `cargo xtask verify --skip-hub-build` (Rust-only change).
126+
- [ ] **Correct the existing bd-2w80 comment.** The comment already posted
127+
claims "cargo's directory-mtime dependency tracking is known to be
128+
unreliable on Windows (NTFS mtime semantics differ from Linux ext4)" —
129+
that framing is Opus's pre-1.50 citation, not confirmed against our
130+
actual cargo version. Post a follow-up comment with the corrected
131+
mechanism once step 1 settles it, before/alongside closing.
132+
- [ ] **Decide bd-2w80 disposition**: close with the confirmed root cause +
133+
fix, or split the `quarto-sass` sibling fix into its own strand
134+
(`discovered-from` link) if it's not bundled into the same PR.
135+
- [ ] **Commit.** Stage `crates/quarto-citeproc/build.rs`,
136+
`crates/quarto-sass/build.rs` (if touched). Do not push without explicit
137+
approval per project git policy.
138+
139+
## Notes / open questions
140+
141+
- Both research agents agree on the *fix* even while disagreeing on the
142+
*mechanism* — low risk either way, but the tracker comment and commit
143+
message should state only what we've actually verified on this cargo
144+
version, not repeat either agent's unverified citation.
145+
- Dispatch-prompt gap noticed mid-investigation: non-fork subagents (Explore,
146+
general-purpose) inherit zero context from `~/.claude/rules/*.md` or project
147+
`CLAUDE.md` — the sonnet research agent used `curl` for GitHub source
148+
reads instead of `gh repo read-file`/`read-dir` because the dispatch prompt
149+
never mentioned that preference. Not fixed as part of this plan; noted for
150+
future dispatch-prompt hygiene.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# The CSL conformance manifest lockfile is byte-compared against a
2+
# generated LF-only string in tests/integration/csl_conformance.rs
3+
# (build_lockfile()). Without this pin, core.autocrlf=true checks the
4+
# file out as CRLF on Windows, producing a false "Lockfile mismatch"
5+
# unrelated to any real enabled_tests.txt/fixture divergence.
6+
tests/csl_conformance.lock text eol=lf
7+
tests/enabled_tests.txt text eol=lf

0 commit comments

Comments
 (0)