From dc9056ed2887802f9785382514c600347bb2325a Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Fri, 23 Oct 2026 22:18:49 +0300 Subject: [PATCH] fix: read the model-directory recheck's relpath with '/' on Windows The v0.11.1 publish matrix failed on every Windows combination (nothing was published): os.path.relpath answers 'models\final' there, and the layout check, which takes the config's '/' form, rejected it, so a run with a nested training.final_model_dir would fail after training, at finalisation. The recheck now converts the separator; on POSIX a backslash is part of a name and that path still fails the resolve() comparison that follows. Two Linux-runnable regression tests, one per mutant (no conversion; no resolve comparison). The [0.11.1] CHANGELOG entry that promised the recheck for nested directories now says it holds on Windows too, and the release record's test count moves from 6769 to 6771. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 ++- docs/roadmap-tr.md | 2 +- docs/roadmap.md | 2 +- docs/roadmap/releases.md | 2 +- forgelm/_artifact_paths.py | 4 +++- tests/test_config.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d87b62..41d1450c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -673,7 +673,8 @@ below before upgrading a pinned CI pipeline.)_ relative model directory, separate from audit, compliance, evaluation, logging, and checkpoint paths. Reject trailing separators/dot components that would put approval staging inside the final directory. Recheck destinations before initialization, saving, finalization, and auto-revert; preserve run records - when deleting a rejected final or staged model, including nested custom model directories. + when deleting a rejected final or staged model, including nested custom model directories (on Windows + too: the recheck reads the platform's `\` separators as the config's `/`). - Derive current top-level module inventories from the source tree in the numerical-claims guard; correct the agent and EN/TR architecture inventories and list the TRL compatibility and strict JSON helpers. - Resolve ORPO and SimPO config/trainer classes from top-level TRL or its experimental ORPO/CPO modules, diff --git a/docs/roadmap-tr.md b/docs/roadmap-tr.md index 1ba17658..9658205c 100644 --- a/docs/roadmap-tr.md +++ b/docs/roadmap-tr.md @@ -21,7 +21,7 @@ > **Durum lejantı:** ✅ Yayınlandı (PyPI) · 🟡 main'e indi, publish bekliyor · 🚧 Sürüyor (iş iniyor; nesi teslim edilip nesi edilmediği için faz dosyasına bakın) · 📋 / ⏳ Planlandı -**Yayınlandı:** `v0.11.1` — "Kapalı Kalan Kapılar" — PyPI 2026-10-23. Maintainer kararıyla, `release.md`'nin bump tablosuna tek seferlik bir istisna olarak patch numarası aldı (tablo `0.12.0` derdi): **on bir kırıcı değişiklik** içerir; sabitlenmiş bir pipeline'ı yükseltmeden önce `### Breaking` bölümünü okuyun. Faz 16'nın ilk üç adımı kalite, safety, judge ve benchmark kapılarını bir ölçümü karşılaştıramadıklarında başarısız kılar — sıradan YAML'da yazılabilen bir `.nan` eşiği, reddetmesi gereken her modeli geçiriyordu — ve `cache-tasks` bir dataset hazırlanamadığında `2` ile çıkar. GDPR, silme, saklama ve PII dokümantasyonu ile altı dildeki site artık kodun yaptığını söyler; `rope_scaling` transformers 5'te yeniden çalışır ve `transformers` alt sınırı yayımlanmış iki güvenlik açığını kapatır. 4584 → 6769 test; 29 → 32 CI guard'ı. Bkz. [releases.md](roadmap/releases.md#v0111--fail-closed-2026-10-23). +**Yayınlandı:** `v0.11.1` — "Kapalı Kalan Kapılar" — PyPI 2026-10-23. Maintainer kararıyla, `release.md`'nin bump tablosuna tek seferlik bir istisna olarak patch numarası aldı (tablo `0.12.0` derdi): **on bir kırıcı değişiklik** içerir; sabitlenmiş bir pipeline'ı yükseltmeden önce `### Breaking` bölümünü okuyun. Faz 16'nın ilk üç adımı kalite, safety, judge ve benchmark kapılarını bir ölçümü karşılaştıramadıklarında başarısız kılar — sıradan YAML'da yazılabilen bir `.nan` eşiği, reddetmesi gereken her modeli geçiriyordu — ve `cache-tasks` bir dataset hazırlanamadığında `2` ile çıkar. GDPR, silme, saklama ve PII dokümantasyonu ile altı dildeki site artık kodun yaptığını söyler; `rope_scaling` transformers 5'te yeniden çalışır ve `transformers` alt sınırı yayımlanmış iki güvenlik açığını kapatır. 4584 → 6771 test; 29 → 32 CI guard'ı. Bkz. [releases.md](roadmap/releases.md#v0111--fail-closed-2026-10-23). **`v0.8.0`** — "Model Bütünlüğü Doğrulaması ve Deprecation Temizliği" — PyPI 2026-06-16. `forgelm verify-integrity`'i ekler (eğitilmiş bir model dizinini Madde 15 `model_integrity.json` manifestine karşı yeniden hash'ler) ve config-driven merge (`merge.ties_trim_fraction` / `dare_drop_rate` / `dare_seed`) ile synthetic-sanity (`synthetic.sanity_failure_rate`) ayarlarını ekler; `distributed.strategy` / `data.mix_ratio` doğrulamasını sertleştirir; `evaluation.staging_ttl_days` (v0.5.5'te deprecated edildi) ve `--data-audit` CLI flag'inin (v0.5.0'da deprecated edildi) deprecation cadence'ini tamamlayarak ikisini de kaldırır. Bir bakım sürümü — yeni bir roadmap fazı eklemez. Bkz. [releases.md](roadmap/releases.md#v080--model-integrity-verification--deprecation-cleanup-2026-06-16). diff --git a/docs/roadmap.md b/docs/roadmap.md index 96ce9491..dd6fe4d0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -21,7 +21,7 @@ > **Status legend:** ✅ Released (PyPI) · 🟡 Merged on main, publish pending · 🚧 In progress (work landing; see the phase file for what is and is not delivered) · 📋 / ⏳ Planned -**Released:** `v0.11.1` — "Fail Closed" — PyPI 2026-10-23. Numbered as a patch by maintainer decision, as a one-time exception to `release.md`'s bump table (which would make it `0.12.0`): it carries **eleven breaking changes**, so read `### Breaking` before upgrading a pinned pipeline. Phase 16's first three steps make the quality, safety, judge and benchmark gates fail when they cannot compare a measurement — a `.nan` threshold in ordinary YAML used to pass every model it was asked to reject — and `cache-tasks` exits `2` when a dataset did not stage. The GDPR, erasure, retention and PII documentation, and the site in six languages, now say what the code does; `rope_scaling` works on transformers 5 again and the `transformers` floor closes two published advisories. 4584 → 6769 tests; 29 → 32 CI guards. See [releases.md](roadmap/releases.md#v0111--fail-closed-2026-10-23). +**Released:** `v0.11.1` — "Fail Closed" — PyPI 2026-10-23. Numbered as a patch by maintainer decision, as a one-time exception to `release.md`'s bump table (which would make it `0.12.0`): it carries **eleven breaking changes**, so read `### Breaking` before upgrading a pinned pipeline. Phase 16's first three steps make the quality, safety, judge and benchmark gates fail when they cannot compare a measurement — a `.nan` threshold in ordinary YAML used to pass every model it was asked to reject — and `cache-tasks` exits `2` when a dataset did not stage. The GDPR, erasure, retention and PII documentation, and the site in six languages, now say what the code does; `rope_scaling` works on transformers 5 again and the `transformers` floor closes two published advisories. 4584 → 6771 tests; 29 → 32 CI guards. See [releases.md](roadmap/releases.md#v0111--fail-closed-2026-10-23). **`v0.8.0`** — "Model Integrity Verification & Deprecation Cleanup" — PyPI 2026-06-16. Adds `forgelm verify-integrity` (re-hashes a trained model directory against its Article 15 `model_integrity.json` manifest) plus config-driven merge (`merge.ties_trim_fraction` / `dare_drop_rate` / `dare_seed`) and synthetic-sanity (`synthetic.sanity_failure_rate`) knobs; hardens `distributed.strategy` / `data.mix_ratio` validation; completes the deprecation cadence for `evaluation.staging_ttl_days` (deprecated v0.5.5) and the `--data-audit` CLI flag (deprecated v0.5.0), removing both. A maintenance release — no new roadmap phase. See [releases.md](roadmap/releases.md#v080--model-integrity-verification--deprecation-cleanup-2026-06-16). diff --git a/docs/roadmap/releases.md b/docs/roadmap/releases.md index 813d6755..b8fbc16b 100644 --- a/docs/roadmap/releases.md +++ b/docs/roadmap/releases.md @@ -358,7 +358,7 @@ The GDPR, erasure, retention, PII-masking, GPU-cost and chat documentation was c New issues are triaged before work starts, dependencies between issues are gated, and a contributor can claim a `good first issue` with `/assign`. The repository gained `CITATION.cff`, `GOVERNANCE.md`, `MAINTAINERS.md`, Dependabot, private security reporting and a 32nd CI guard, `tools/check_ci_failure_masking.py`. -`__api_version__` 1.1.0 → 1.2.0: `TrainResult` gained `judge_passed`, an additive field on a stable dataclass. 4584 → 6769 tests collected (124 → 133 test modules); 29 → 32 CI guards. +`__api_version__` 1.1.0 → 1.2.0: `TrainResult` gained `judge_passed`, an additive field on a stable dataclass. 4584 → 6771 tests collected (124 → 133 test modules); 29 → 32 CI guards. ### Full changelog diff --git a/forgelm/_artifact_paths.py b/forgelm/_artifact_paths.py index b4e69fca..f69a2493 100644 --- a/forgelm/_artifact_paths.py +++ b/forgelm/_artifact_paths.py @@ -112,7 +112,9 @@ def _check_model_destination(output_dir: str, final_path: str, config: Any) -> N from .config import ConfigError try: - relative = os.path.relpath(final_path, output_dir) + # relpath answers with the platform separator, '\' on Windows, while the layout check takes the config's + # '/' form. On POSIX a backslash is part of a name; the resolve comparison below still rejects that path. + relative = os.path.relpath(final_path, output_dir).replace("\\", "/") expected = _validate_layout(output_dir, relative, _benchmark_output_dir(config)) # relpath is lexical; do not let normalization conceal a symlink/.. # path whose actual destination differs from the checked directory. diff --git a/tests/test_config.py b/tests/test_config.py index 32fb8b74..5f14359f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -370,6 +370,35 @@ def test_dry_run_rejects_benchmark_overlap_before_output_creation(self, tmp_path assert raised.value.code == 1 assert not output.exists() + def test_runtime_recheck_accepts_a_windows_relpath(self, tmp_path, minimal_config, monkeypatch): + """On Windows ``os.path.relpath`` answers ``models\\final``; the recheck before saving must read it as the + configured ``models/final`` rather than fail the finished run (found by the v0.11.1 cross-OS matrix).""" + import forgelm._artifact_paths as artifact_paths + + output = tmp_path / "output" + config = ForgeConfig.model_validate( + minimal_config(training={"output_dir": str(output), "final_model_dir": "models/final"}) + ) + real_relpath = os.path.relpath + monkeypatch.setattr( + artifact_paths.os.path, "relpath", lambda path, start: real_relpath(path, start).replace("/", "\\") + ) + artifact_paths._check_model_destination(str(output), str(output / "models" / "final"), config) + artifact_paths._check_model_destination(str(output), str(output / "models" / "final.staging.fg-1"), config) + + @pytest.mark.skipif(os.name == "nt", reason="a backslash is a separator on Windows") + def test_runtime_recheck_rejects_a_backslash_in_a_posix_name(self, tmp_path, minimal_config): + """On POSIX ``models\\final`` is one directory name, not ``models/final``: reading the separator as ``/`` + must not let it pass the recheck.""" + import forgelm._artifact_paths as artifact_paths + + output = tmp_path / "output" + config = ForgeConfig.model_validate( + minimal_config(training={"output_dir": str(output), "final_model_dir": "models/final"}) + ) + with pytest.raises(ConfigError): + artifact_paths._check_model_destination(str(output), str(output / "models\\final"), config) + @pytest.mark.parametrize( "value", [