From 8840cb2e690490cd7e284966e7542f67d56186ae Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 13 Aug 2026 22:08:34 +1000 Subject: [PATCH 1/2] fix(doctor): order diagnostic references deterministically Several diagnostics can share a name, so sorting on provider and name alone left the order to settle on set iteration order. Two identical `ref doctor` runs then produced different reports. Adds the slug as the tiebreak, and drops a docstring reference to a `--only` flag that does not exist. --- .../climate-ref-core/src/climate_ref_core/summary.py | 6 +++--- packages/climate-ref-core/tests/unit/test_summary.py | 11 +++++++++++ .../climate-ref/src/climate_ref/doctor/registry.py | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/climate-ref-core/src/climate_ref_core/summary.py b/packages/climate-ref-core/src/climate_ref_core/summary.py index c08bd9fcf..da43c4402 100644 --- a/packages/climate-ref-core/src/climate_ref_core/summary.py +++ b/packages/climate-ref-core/src/climate_ref_core/summary.py @@ -267,9 +267,9 @@ def label(self) -> str: return f"{self.name} ({self.provider_slug})" @property - def sort_key(self) -> str: - """Sort by provider then name.""" - return f"{self.provider_slug}/{self.name}" + def sort_key(self) -> tuple[str, str, str]: + """Sort by provider, then name, then slug. Several diagnostics can share a name.""" + return (self.provider_slug, self.name, self.slug) @frozen diff --git a/packages/climate-ref-core/tests/unit/test_summary.py b/packages/climate-ref-core/tests/unit/test_summary.py index b1f45938b..194ba496a 100644 --- a/packages/climate-ref-core/tests/unit/test_summary.py +++ b/packages/climate-ref-core/tests/unit/test_summary.py @@ -451,6 +451,17 @@ def test_diagnostics_tracked_per_variable(self, simple_provider): assert ref.provider_slug == "test-provider" assert ref.label == "Simple Diagnostic (test-provider)" + def test_sort_key_breaks_ties_on_slug(self): + """Several diagnostics can share a name, so the slug has to settle the order.""" + shared = [ + DiagnosticReference(name="Ozone Diagnostics", slug=slug, provider_slug="esmvaltool") + for slug in ("ozone-sh-oct", "ozone-annual-cycle", "ozone-nh-mar") + ] + + ordered = sorted(shared, key=lambda r: r.sort_key) + + assert [r.slug for r in ordered] == ["ozone-annual-cycle", "ozone-nh-mar", "ozone-sh-oct"] + def test_empty_providers(self): result = collect_by_source_type([]) assert result == [] diff --git a/packages/climate-ref/src/climate_ref/doctor/registry.py b/packages/climate-ref/src/climate_ref/doctor/registry.py index cac99f1ba..8885eedc7 100644 --- a/packages/climate-ref/src/climate_ref/doctor/registry.py +++ b/packages/climate-ref/src/climate_ref/doctor/registry.py @@ -81,7 +81,7 @@ def register_check(registered: RegisteredCheck) -> None: ------ ValueError If another check already claims the same slug. Two checks sharing a slug would be - indistinguishable in the output and in ``--only``. + indistinguishable in the report. """ existing = _REGISTRY.get(registered.slug) if existing is not None: From 12986f109b814b08c97e4a253198dbb155d4581b Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 13 Aug 2026 22:08:58 +1000 Subject: [PATCH 2/2] docs: add changelog entry --- changelog/870.fix.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/870.fix.md diff --git a/changelog/870.fix.md b/changelog/870.fix.md new file mode 100644 index 000000000..d94dc289a --- /dev/null +++ b/changelog/870.fix.md @@ -0,0 +1,2 @@ +Fixed `ref doctor` producing a different report on each run. +Diagnostics that share a name were ordered by set iteration order, so two identical runs disagreed.