Skip to content

feat: diff generated corpus output against an earlier ref - #73

Merged
lightsofapollo merged 4 commits into
mainfrom
feat/corpus-gen-diff
Sep 8, 2026
Merged

feat: diff generated corpus output against an earlier ref#73
lightsofapollo merged 4 commits into
mainfrom
feat/corpus-gen-diff

Conversation

@lightsofapollo

Copy link
Copy Markdown
Contributor

Changing the generator can silently move the code emitted for the 58 real-world specs in specs/, and nothing showed what moved.

Generation is byte-deterministic and the specs are pinned, so the old output does not need to be stored — it is rebuilt on demand. The full corpus regenerates in about 30 seconds locally, against 300 MB if it were checked in (microsoft-graph alone is 113 MB).

scripts/gen-diff.sh <base-ref> builds the generator at that ref in a throwaway worktree, regenerates both corpora, and reports per-spec churn plus any public item that appeared or disappeared. Full diffs land in tmp/gen-diff/report/, capped at 5 MB each. The base side is cached per commit, keyed on the spec set it holds.

tests/corpus-manifest.txt (21 KB) is the committed tripwire: one hashed line per generated file. The Generated by openapi-to-rust vX.Y.Z stamp is normalized away, so a version bump alone never touches it.

CI: pull requests get the corpus-diff job — it diffs against the PR base, puts the per-spec table in the job summary, uploads the full diffs as an artifact, and then verifies the manifest against the corpus it already generated. Pushes to main and scheduled runs verify the manifest on its own.

Both cover types and client output. Server scaffolding is generated from per-spec [server].operations selectors, so it has no uniform corpus pass yet.

For reference, gen-diff.sh v0.15.0 on the merged union fixes reports 24 specs changed, the largest being 3,688 microsoft-graph aliases moving from serde_json::Value to BTreeMap<String, serde_json::Value> with no public item added or removed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3eDyWLLqUtCzynJeLMAXL

lightsofapollo and others added 2 commits September 8, 2026 11:45
Changing the generator can silently move the code emitted for the 58
real-world specs in specs/, and nothing showed what moved.

Generation is byte-deterministic and the specs are pinned, so the old
output does not need to be stored — it is rebuilt on demand. The full
corpus regenerates in about 30 seconds, against 300 MB if it were
checked in.

scripts/gen-diff.sh <base-ref> builds the generator at that ref in a
throwaway worktree, regenerates both corpora, and reports per-spec churn
plus any public item that appeared or disappeared; full diffs land in
tmp/gen-diff/report/. The base side is cached per commit.

tests/corpus-manifest.txt is the committed tripwire: one hashed line per
generated file (21 KB), verified in CI by corpus-manifest.sh --check, so
a change that moves real-world output cannot land unnoticed. The
"Generated by openapi-to-rust vX.Y.Z" stamp is normalized away so a
version bump alone never touches it.

Both share scripts/lib/corpus.sh, which owns everything that decides the
generated bytes — the per-spec TOML mirrors spec-compile.sh, so the
manifest describes the same code CI compiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3eDyWLLqUtCzynJeLMAXL
A pass/fail tripwire tells a reviewer that real-world output moved but not
what moved. The corpus-diff job regenerates the corpus at the base of the
pull request and at its head, puts the per-spec table in the job summary,
and uploads the full diffs as an artifact.

That job already generates the head corpus, so the manifest gate now runs
off it (`--check --from tmp/gen-diff/head`) instead of generating a third
copy; the standalone manifest job is left to pushes and scheduled runs.
Both sides also build into one cargo target dir now, so the dependency
graph compiles once per run rather than twice.

Fixes a cache bug the tool found in itself: the base corpus was keyed on
the commit alone, so a corpus left behind by an earlier GEN_DIFF_SPECS run
was reused for a wider run and every spec it lacked read as new. The marker
now records which specs the cache holds, and the comparison walks the
requested spec list rather than whatever directories happen to exist.

Per-spec diffs are capped at 5 MB (GEN_DIFF_MAX_DIFF_BYTES=0 to disable) so
a sweeping change cannot produce a report nobody can open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3eDyWLLqUtCzynJeLMAXL
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
openapi-to-rust Ready Ready Preview Sep 8, 2026 6:29pm UTC

Request Review

CI caught the manifest failing on a pull request that changes no
generated code. The lines were identical — same bytes, same hashes — but
in a different order: `sort` collates by locale, so macOS orders
anthropic/REQUIRED_DEPS.toml after anthropic/client.rs and a C-locale
runner orders it before. LC_ALL=C in the shared library fixes every
ordering the tooling produces, the manifest included; it is regenerated
here in byte order.

Worth recording what the failure also proved: the generated code itself
is byte-identical on macOS and Linux, and identical between debug and
release builds of the generator. Every hash matched across all three.

spec-compile.sh wrote its own copy of the generator config, identical to
the corpus tooling's by copy-paste alone. Both now call corpus_config, so
the code the manifest hashes cannot drift from the code the compile gate
checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3eDyWLLqUtCzynJeLMAXL
A warm local run goes from 11m40s to 7m25s: nextest runs each test in its
own process, where the built-in harness runs one process per test binary
and serializes across binaries. All 679 tests pass unchanged, so nothing
depended on state shared within a binary.

Doctests still go through `cargo test --doc`. nextest cannot run them, and
this crate has seven — swapping the command outright would have dropped
them silently.

The conformance report step converts as-is: each of those tests builds its
results and writes the report inside one test function, so process
isolation does not split it.

.config/nextest.toml raises the slow-test threshold to 120s, since tests
that generate a crate and compile it legitimately run for minutes (the
longest takes 428s), and adds a ci profile that reports every failure in
one run instead of stopping at the first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3eDyWLLqUtCzynJeLMAXL
@lightsofapollo
lightsofapollo merged commit 99e4b18 into main Sep 8, 2026
14 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.

1 participant