From f408eb005bc4ba4fb2c73a4481626c792396d9da Mon Sep 17 00:00:00 2001 From: iceteaSA <171169159+iceteaSA@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:22:08 +0200 Subject: [PATCH] test(store): pin the schema-version derivation by its invariant The manifest-side assertion only checks that store_schema_version parses to a positive number, so a regressed derivation that still returns a positive value is invisible there. Verified by mutation: changing newest_schema_version to .min() left all 22 tests in the area green. The fix is not to restate the derivation in the test -- that would make the test agree with the code by construction and pass whatever the function did. It asserts the two bounds the result must satisfy: no migration exceeds it, and it is a version the list actually contains. .last() stays green while the list is sorted, because it then returns the same value as .max(). That is an equivalent mutant rather than a gap, and the first bound catches it as soon as the list is unsorted -- verified, not asserted, by reversing the iteration order. Reported by cubic on #6. The finding was right; its stated reason was not -- the list holds 12 migrations, not one, and .min() survives because 1 is still greater than 0. --- crates/synapse-module/src/store.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/synapse-module/src/store.rs b/crates/synapse-module/src/store.rs index d6fbd3ad..b42e7256 100644 --- a/crates/synapse-module/src/store.rs +++ b/crates/synapse-module/src/store.rs @@ -8327,6 +8327,36 @@ mod tests { let _ = std::fs::remove_dir_all(root); } + #[test] + fn newest_schema_version_is_the_highest_migration_this_binary_carries() { + // Asserts the PROPERTY the derivation must satisfy, not the derivation + // itself. Restating `MIGRATIONS.iter().map(..).max()` here would make + // this agree with the code by construction and pass whatever the + // function did; the manifest-side check can only see shape, so a + // regressed derivation that still returns a positive number is + // invisible there. These two bounds pin it from both sides: `.min()` + // or a hardcoded low literal breaks the first, and a literal above the + // list breaks the second. + // + // `.last()` is NOT caught while the list stays sorted, because it then + // returns the same value as `.max()` -- an equivalent mutant, not a + // gap. It becomes a real regression only once the list is unsorted, + // and the first bound catches it exactly then. + let newest = newest_schema_version(); + assert!( + MIGRATIONS + .iter() + .all(|migration| migration.version <= newest), + "newest_schema_version() reported {newest}, which is below a migration this binary carries" + ); + assert!( + MIGRATIONS + .iter() + .any(|migration| migration.version == newest), + "newest_schema_version() reported {newest}, which is not a migration this binary carries" + ); + } + #[test] fn schema_trigger_set_is_exactly_the_known_guards() { // The migration fence above can only exercise guards that exist when it