From 086cfb2147928b8bfd2b355371a4934a123948a5 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Tue, 16 Jun 2026 01:18:39 +0300 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20release=20v0.8.0=20=E2=80=94=20ver?= =?UTF-8?q?ify-integrity=20+=20remove=20deprecated=20--data-audit=20/=20ev?= =?UTF-8?q?aluation.staging=5Fttl=5Fdays?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MINOR release (`__version__` 0.7.0 → 0.8.0, `__api_version__` 1.0.0 → 1.1.0). Added (feature + public API): - `forgelm verify-integrity MODEL_DIR` subcommand + `forgelm.verify_integrity()` / `VerifyIntegrityResult` stable library API (Article 15 model-integrity manifest verification). Added to `forgelm.__all__` → MINOR `__api_version__` bump (snapshot regenerated as tests/_data/api_signatures_1.1.0.json). Removed (deprecated in v0.7.0, removal due this minor per the one-minor cadence): - `evaluation.staging_ttl_days` config field (alias). `retention.staging_ttl_days` is now the only form; `EvaluationConfig` is `extra="forbid"` so the legacy key raises a validation error. Removed the reconciliation/alias-forward machinery. - `forgelm --data-audit PATH` CLI flag. Use the `forgelm audit PATH` subcommand. Removed the legacy dispatch path + the now-unemitted `cli.legacy_flag_invoked` audit event (dropped from the catalog). Release mechanics: - CHANGELOG [Unreleased] → [0.8.0] (Added/Changed/Deprecated/Removed/Fixed). - pyproject `version = "0.8.0"`; site version literals re-derived (update_site_version). - Notebook pins bumped 0.7.0 → 0.8.0. - Deprecation-behaviour tests replaced with removal-assertion tests; docs (EN+TR) updated to past-tense removal; historical roadmap/design records untouched. Verified: ruff, full pytest (2856 passed), 12 doc/CI guards, dry-run, fresh-install `forgelm --version` = 0.8.0 — all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 30 ++- config_template.yaml | 3 +- docs/guides/data_audit-tr.md | 9 - docs/guides/data_audit.md | 8 - docs/guides/gdpr_erasure-tr.md | 12 +- docs/guides/gdpr_erasure.md | 12 +- docs/qms/sop_data_management-tr.md | 2 +- docs/qms/sop_data_management.md | 2 +- docs/reference/audit_event_catalog-tr.md | 1 - docs/reference/audit_event_catalog.md | 1 - docs/reference/configuration-tr.md | 19 +- docs/reference/configuration.md | 18 +- .../soc2_trust_criteria_mapping-tr.md | 2 +- docs/reference/soc2_trust_criteria_mapping.md | 2 +- docs/reference/usage-tr.md | 3 - docs/reference/usage.md | 3 - docs/usermanuals/en/reference/cli.md | 3 +- docs/usermanuals/tr/reference/cli.md | 3 +- forgelm/_version.py | 9 +- forgelm/cli/_dispatch.py | 84 +------ forgelm/cli/_parser.py | 18 +- forgelm/cli/subcommands/_audit.py | 16 +- forgelm/compliance.py | 2 +- forgelm/config.py | 172 ++------------- notebooks/data_curation.ipynb | 4 +- notebooks/dpo_alignment.ipynb | 2 +- notebooks/galore_memory_optimization.ipynb | 2 +- notebooks/grpo_reasoning.ipynb | 2 +- notebooks/ingestion_playground.ipynb | 2 +- notebooks/kto_binary_feedback.ipynb | 2 +- notebooks/multi_dataset.ipynb | 2 +- notebooks/post_training_workflow.ipynb | 6 +- notebooks/quickstart_sft.ipynb | 2 +- notebooks/safety_evaluation.ipynb | 2 +- notebooks/synthetic_data_training.ipynb | 2 +- pyproject.toml | 2 +- site/compliance.html | 2 +- site/contact.html | 2 +- site/features.html | 2 +- site/guide.html | 2 +- site/index.html | 4 +- site/js/translations.js | 12 +- site/privacy.html | 2 +- site/quickstart.html | 10 +- site/terms.html | 2 +- tests/_data/api_signatures_1.1.0.json | 188 ++++++++++++++++ tests/test_cli_subcommands.py | 89 +------- tests/test_config.py | 20 +- tests/test_gdpr_erasure.py | 206 ------------------ 49 files changed, 322 insertions(+), 683 deletions(-) create mode 100644 tests/_data/api_signatures_1.1.0.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b91786f3..467bf520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,19 @@ All notable changes to ForgeLM are documented here. ## [Unreleased] -_(v0.7.1 dev cycle — entries will land here as PRs merge.)_ +_(v0.8.1 dev cycle — entries will land here as PRs merge.)_ + +## [0.8.0] — 2026-06-16 ### Added +- **Model-integrity verification.** New `forgelm verify-integrity MODEL_DIR` + subcommand and `forgelm.verify_integrity()` / `VerifyIntegrityResult` public + API: re-hashes a trained model directory against its EU AI Act Article 15 + `model_integrity.json` SHA-256 manifest (written by the compliance export at + training time) and reports `changed` / `removed` / `added` artifacts. Exit + `0` (all match) / `1` (mismatch or input error) / `2` (runtime I/O failure); + `--output-format json` for CI gates. Bumps `__api_version__` to `1.1.0`. - **Config-driven merge hyperparameters.** `merge.ties_trim_fraction`, `merge.dare_drop_rate`, and `merge.dare_seed` expose the TIES/DARE knobs that were previously fixed module constants (defaults unchanged: `0.2`, @@ -26,13 +35,6 @@ _(v0.7.1 dev cycle — entries will land here as PRs merge.)_ weight per dataset (`1 primary + len(extra_datasets)`); a length mismatch raised no config error and silently fell back to uniform mixing at runtime. Both now fail fast at config time (exit 1). -- **Deprecation removals deferred to v0.8.0.** `evaluation.staging_ttl_days` - and the `--data-audit` CLI flag were originally scheduled for removal in - v0.7.0, but v0.7.0 shipped with both still present to preserve the full - one-minor deprecation window. Every `DeprecationWarning`, `--help` text, - reference-doc note, and the release-standard worked example now name - **v0.8.0** consistently as the removal target. The `### Removed` section - lands in the v0.8.0 CHANGELOG. ### Deprecated @@ -43,6 +45,18 @@ _(v0.7.1 dev cycle — entries will land here as PRs merge.)_ `packing` instead — `sample_packing` is removed in **v0.9.0**. See [docs/standards/release.md](docs/standards/release.md#deprecation-cadence). +### Removed + +- **`evaluation.staging_ttl_days`** (deprecated in v0.7.0) is removed. Use the + canonical `retention.staging_ttl_days`; `EvaluationConfig` is `extra="forbid"`, + so the legacy key now raises a validation error instead of forwarding. +- **`forgelm --data-audit PATH`** CLI flag (deprecated in v0.7.0) is removed. + Use the first-class `forgelm audit PATH` subcommand — identical behaviour and + output. `argparse` now rejects the flag (exit 2). +- **`cli.legacy_flag_invoked`** audit event is no longer emitted (it recorded + use of the removed `--data-audit` flag) and has been dropped from the + audit-event catalog. + ### Fixed - **Eval artefact privacy-redaction documented.** `safety_results.json` and diff --git a/config_template.yaml b/config_template.yaml index b7b2d918..58f6f1ea 100644 --- a/config_template.yaml +++ b/config_template.yaml @@ -224,7 +224,8 @@ data: # check_interval_hours: 24 # Retention horizons — GDPR Article 5(1)(e) "storage limitation" + Article 17 -# "right to erasure". Replaces deprecated `evaluation.staging_ttl_days`. +# "right to erasure". `staging_ttl_days` is the canonical staging-retention +# field (the legacy `evaluation.staging_ttl_days` alias was removed in v0.8.0). # retention: # audit_log_retention_days: 1825 # ~5 years (Article 12 record-keeping baseline) # staging_ttl_days: 7 # final_model.staging./ TTL after `forgelm reject` diff --git a/docs/guides/data_audit-tr.md b/docs/guides/data_audit-tr.md index 5cc4981c..196391be 100644 --- a/docs/guides/data_audit-tr.md +++ b/docs/guides/data_audit-tr.md @@ -47,10 +47,6 @@ forgelm audit data/ --quality-filter forgelm audit data/ --workers 4 ``` -> **Eski alias:** `forgelm --data-audit PATH` deprecation alias'ı -> olarak korunuyor; çalışmaya devam ediyor ama bir uyarı log'lanıyor. -> Yeni script'lerde `audit` subcommand'ını kullanın. - `--output` varsayılan olarak `./audit/`'tir. Dizin yoksa oluşturulur; **tam** `data_audit_report.json` her zaman oraya yazılır. Stdout varsayılan olarak insan-okunabilir özet gösterir; `--output-format json` @@ -529,11 +525,6 @@ default-AÇIK**) heuristic kalite skorlamasını çalıştırır; operatörler için opt-out sağlar. Credential/secrets taraması **her zaman açık** — kapatma flag'i yoktur. -Eski `forgelm --data-audit PATH` flag'i deprecation alias olarak -korunuyor; davranış aynı, sadece ek bir uyarı log'lanıyor. - -Üst düzey flag'tir (subcommand değil) — trainer'a dokunmadan çıkar. - > **Not:** Bu davranış kılavuzun başındaki özetle eşleşir: > `--output-format json` stdout'a küçük bir zarf (success flag, üst > seviye metrikler, rapor yolu) yazar. Tam `data_audit_report.json` diff --git a/docs/guides/data_audit.md b/docs/guides/data_audit.md index 4a3e7c0e..4891ada6 100644 --- a/docs/guides/data_audit.md +++ b/docs/guides/data_audit.md @@ -47,10 +47,6 @@ forgelm audit data/ --quality-filter forgelm audit data/ --workers 4 ``` -> **Legacy alias:** `forgelm --data-audit PATH` keeps working unchanged -> as a deprecation alias and logs a one-line notice. New scripts should -> use the `audit` subcommand. Removal targeted no earlier than `v0.7.0`. - `--output` defaults to `./audit/`. The directory is created if missing; the **full** `data_audit_report.json` is always written there. Stdout shows a human-readable summary by default; pass `--output-format json` to get @@ -527,10 +523,6 @@ invariant is pinned by `tests/test_data_audit_workers.py` so a CI gate that compares the audit-report hash across runs continues to work when an operator switches from `--workers 1` to `--workers 4`. -The legacy `forgelm --data-audit PATH` flag is preserved as a -deprecation alias and logs a one-line notice. Behaviour is identical; -new scripts should use the subcommand. - --- ## Troubleshooting diff --git a/docs/guides/gdpr_erasure-tr.md b/docs/guides/gdpr_erasure-tr.md index acac00dc..4971c6bf 100644 --- a/docs/guides/gdpr_erasure-tr.md +++ b/docs/guides/gdpr_erasure-tr.md @@ -83,17 +83,9 @@ retention: Herhangi bir horizon'u `0` olarak ayarlamak o artefact kind'ı için policy'yi devre dışı bırakır (sınırsız tut). `enforce`, trainer pre-flight gate'inin ihlallere nasıl tepki vereceğini kontrol eder. -## Deprecation: `evaluation.staging_ttl_days` +## Kaldırıldı: `evaluation.staging_ttl_days` -Wave 1 `evaluation.staging_ttl_days` field'ı (v0.5.5'te shipped) **deprecate** edildi. Yerine `retention.staging_ttl_days` kullan: - -- "Set" kararı Pydantic v2'nin `model_fields_set` setine bakılarak verilir: bir field YAML'de açıkça yazıldıysa (örn. `evaluation.staging_ttl_days: 7`) Pydantic'in default'la doldurmasından bağımsız olarak "set" sayılır. Bu, "default değerle eşit" gibi heuristik tahminlerin operatörü yanıltmasını engeller. -- Sadece legacy field açıkça set'liyse `retention.staging_ttl_days`'e alias-forward edilir ve tek bir `DeprecationWarning` yayılır. -- Sadece canonical field açıkça set'liyse sessiz canonical path. -- İkisi de **aynı** değerlerle açıkça set'liyse `DeprecationWarning` yayılır (canonical block kazanır). -- İkisi de **farklı** değerlerle açıkça set'liyse config-load zamanında `ConfigError` raise edilir. Sessiz kazanan = yanlış kazanan. - -Deprecate edilen field **v0.8.0**'da kaldırılır. +`evaluation.staging_ttl_days` field'ı (başlangıçta v0.5.5'te shipped) **v0.8.0'da kaldırıldı**. Bunun yerine kanonik field olan `retention.staging_ttl_days` kullanın. `evaluation.staging_ttl_days` hâlâ set edilmiş YAML dosyaları v0.8.0 ve sonrasında `EXIT_CONFIG_ERROR` ile config-load başarısızlığına yol açar. ## Audit-event sözlüğü diff --git a/docs/guides/gdpr_erasure.md b/docs/guides/gdpr_erasure.md index 7998d6ee..24941879 100644 --- a/docs/guides/gdpr_erasure.md +++ b/docs/guides/gdpr_erasure.md @@ -83,17 +83,9 @@ retention: Setting any horizon to `0` disables the policy for that artefact kind (retain indefinitely). `enforce` controls how the trainer pre-flight gate reacts to violations. -## Deprecation: `evaluation.staging_ttl_days` +## Removed: `evaluation.staging_ttl_days` -The Wave 1 `evaluation.staging_ttl_days` field (shipped in v0.5.5) is **deprecated**. Use `retention.staging_ttl_days` instead: - -- "Set" is determined by Pydantic v2's `model_fields_set`: a field is "set" when the operator wrote it in YAML (e.g. `evaluation.staging_ttl_days: 7`), independent of whether the value matches the Pydantic default. This replaces the earlier "non-default value" heuristic that mishandled operators who explicitly re-stated the default. -- Setting only the legacy field explicitly alias-forwards to `retention.staging_ttl_days` and emits a single `DeprecationWarning`. -- Setting only the canonical field explicitly is the silent canonical path. -- Setting both explicitly with **identical** values emits a `DeprecationWarning` (the canonical block wins). -- Setting both explicitly with **different** values raises `ConfigError` at config-load time. Silent winner = wrong winner. - -The deprecated field is removed in **v0.8.0**. +The `evaluation.staging_ttl_days` field (originally shipped in v0.5.5) was **removed in v0.8.0**. Use `retention.staging_ttl_days` — the canonical field — instead. YAML files that still set `evaluation.staging_ttl_days` will fail config-load with `EXIT_CONFIG_ERROR` in v0.8.0 and later. ## Audit-event vocabulary diff --git a/docs/qms/sop_data_management-tr.md b/docs/qms/sop_data_management-tr.md index 42f83b04..32d8f8ac 100644 --- a/docs/qms/sop_data_management-tr.md +++ b/docs/qms/sop_data_management-tr.md @@ -73,7 +73,7 @@ ForgeLM otomatik kontroller: - Trainer tipine göre format doğrulama (SFT, DPO, KTO, GRPO) - Metin temizleme (`clean_text: true`) - **ForgeLM audit pipeline (v0.5.0+, `forgelm audit `; legacy - `forgelm --data-audit` deprecated, v0.8.0'da kaldırılma planlandı)** — + `forgelm --data-audit` flag'i v0.8.0'da kaldırıldı)** — per-split sample sayıları, uzunluk dağılımı, top-3 dil tespiti, near-duplicate oranı (varsayılan 64-bit simhash, veya >50K-row corpora için `--dedup-method minhash` üzerinden **MinHash LSH**), diff --git a/docs/qms/sop_data_management.md b/docs/qms/sop_data_management.md index ffba402e..9d3250d7 100644 --- a/docs/qms/sop_data_management.md +++ b/docs/qms/sop_data_management.md @@ -72,7 +72,7 @@ ForgeLM automated checks: - Format validation per trainer type (SFT, DPO, KTO, GRPO) - Text cleaning (`clean_text: true`) - **ForgeLM audit pipeline (v0.5.0+, `forgelm audit `; legacy - `forgelm --data-audit` deprecated, removal scheduled v0.8.0)** — + `forgelm --data-audit` flag removed in v0.8.0)** — produces `data_audit_report.json` with per-split sample counts, length distribution, top-3 language detection, near-duplicate rate (default 64-bit simhash, or **MinHash LSH** via diff --git a/docs/reference/audit_event_catalog-tr.md b/docs/reference/audit_event_catalog-tr.md index 521e7370..6b00e85e 100644 --- a/docs/reference/audit_event_catalog-tr.md +++ b/docs/reference/audit_event_catalog-tr.md @@ -102,7 +102,6 @@ Hash zinciri, satır diske düştükten (`flush` + `fsync`) sonra ilerler; kirli | Event | Ne zaman emit edilir | Payload | Madde | |-----------------------------|--------------------------------------------------------------------------------------------------------|----------------------------------|-------| -| `cli.legacy_flag_invoked` | Deprecated bir CLI flag'i kullanıldı (örn. `--data-audit`). | `flag`, `replacement`, `version` | 12 | ### Audit-sistem event'leri (meta) diff --git a/docs/reference/audit_event_catalog.md b/docs/reference/audit_event_catalog.md index dc63c10f..a64ee504 100644 --- a/docs/reference/audit_event_catalog.md +++ b/docs/reference/audit_event_catalog.md @@ -102,7 +102,6 @@ The hash chain advances after the line lands on disk (`flush` + `fsync`), so an | Event | When emitted | Payload | Article | |-----------------------------|----------------------------------------------------------------------------------------------------|----------------------------------|---------| -| `cli.legacy_flag_invoked` | A deprecated CLI flag was used (e.g., `--data-audit`). | `flag`, `replacement`, `version` | 12 | ### Audit-system events (meta) diff --git a/docs/reference/configuration-tr.md b/docs/reference/configuration-tr.md index 5191b4f9..d05a5522 100644 --- a/docs/reference/configuration-tr.md +++ b/docs/reference/configuration-tr.md @@ -216,11 +216,11 @@ training: > Limitler sabittir (henüz config ile ayarlanamaz) — uzun biçimli ince ayarlar > için `min_score` ayarlarken bunu göz önünde bulundurun. > -> **Kullanımdan kaldırıldı:** `evaluation.staging_ttl_days`, +> **Kaldırıldı:** `evaluation.staging_ttl_days`, > [`retention.staging_ttl_days`](#retention-isteğe-bağlı--gdpr-madde-17-silme-ufukları) -> tarafından devralınmıştır. Eski anahtar `DeprecationWarning` ile alias-forward -> edilir ve v0.8.0'da kaldırılır. -> Bkz. [release.md](../standards/release.md#deprecation-cadence). +> tarafından devralınmış ve v0.8.0'da kaldırılmıştır. `retention.staging_ttl_days` +> kullanın; eski anahtarı hâlâ set eden YAML dosyaları `EXIT_CONFIG_ERROR` ile +> config-load başarısızlığına yol açar. --- @@ -236,16 +236,15 @@ uzatmasını engeller. | Alan | Tip | Varsayılan | Açıklama | |------|-----|-----------|----------| | `audit_log_retention_days` | int | `1825` (~5 yıl) | `audit_log.jsonl` dosyasının Madde 5(1)(e) kapsamında "geciken" olarak işaretlenmeden önce saklanacağı gün sayısı. `0` süresiz saklamayı belirtir (Madde 17(3)(b) savunması). | -| `staging_ttl_days` | int | `7` | `forgelm reject` kararından sonra `final_model.staging./` dizininin planlı temizlenmeden önce saklanacağı gün sayısı. `0` süresiz saklama anlamına gelir. Kullanımdan kaldırılan `evaluation.staging_ttl_days` yerine geçer; deprecation penceresinde her iki anahtar da aynı değerlerle kabul edilir (eski anahtar v0.8.0'da kaldırılır). | +| `staging_ttl_days` | int | `7` | `forgelm reject` kararından sonra `final_model.staging./` dizininin planlı temizlenmeden önce saklanacağı gün sayısı. `0` süresiz saklama anlamına gelir. v0.8.0'da kaldırılan `evaluation.staging_ttl_days` yerine geçer. | | `ephemeral_artefact_retention_days` | int | `90` | Uyumluluk paketleri, veri denetim raporları ve diğer çalışma kapsamlı türetilmiş artefaktların saklanma süresi (gün). `0` süresiz saklama. | | `raw_documents_retention_days` | int | `90` | İngest edilmiş ham belgelerin (PDF / DOCX / EPUB / TXT / Markdown) operatörün ingestion-output dizininde saklanma süresi (gün). `0` süresiz saklama. | | `enforce` | string | `"log_only"` | Politika uygulama modu: `"log_only"` (yalnızca audit log), `"warn_on_excess"` (stderr'e yapılandırılmış uyarı), `"block_on_excess"` (`EXIT_EVAL_FAILURE` = 3 ile trainer ön-kontrolünü iptal eder). | -> **Kullanımdan kaldırma:** `evaluation.staging_ttl_days`, v0.5.5 itibarıyla -> `retention.staging_ttl_days` lehine kullanımdan kaldırılmıştır. Eski anahtar -> v0.8.0'daki kaldırılışına kadar `DeprecationWarning` ile alias-forward edilir. -> Tam deprecation politikası için -> [release.md](../standards/release.md#deprecation-cadence). +> **Kaldırıldı:** `evaluation.staging_ttl_days` (v0.5.5 itibarıyla kullanımdan +> kaldırılmıştı) v0.8.0'da kaldırılmıştır. Artık geçerli tek form +> `retention.staging_ttl_days`'dir. Eski anahtarı hâlâ set eden YAML dosyaları +> `EXIT_CONFIG_ERROR` ile config-load başarısızlığına yol açar. --- diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 23e3b9f5..235e78df 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -226,10 +226,10 @@ across retries. Each retry attempt is logged to the audit trail. > are fixed (not yet config-driven) — keep this in mind when tuning `min_score` > for long-form fine-tunes. > -> **Deprecated:** `evaluation.staging_ttl_days` is superseded by -> [`retention.staging_ttl_days`](#retention-optional--gdpr-article-17-erasure-horizons). -> The legacy key is alias-forwarded with a `DeprecationWarning` and removed in -> v0.8.0. See [release.md](../standards/release.md#deprecation-cadence). +> **Removed:** `evaluation.staging_ttl_days` was superseded by +> [`retention.staging_ttl_days`](#retention-optional--gdpr-article-17-erasure-horizons) +> and was removed in v0.8.0. Use `retention.staging_ttl_days`; YAML files that +> still set the legacy key will fail config-load with `EXIT_CONFIG_ERROR`. --- @@ -244,16 +244,14 @@ silently extend the retention horizon by re-using a stale workspace. | Field | Type | Default | Description | |-------|------|---------|-------------| | `audit_log_retention_days` | int | `1825` (~5 years) | Days to retain `audit_log.jsonl` before flagging it as overdue under Article 5(1)(e). Set to `0` to retain indefinitely (Article 17(3)(b) defence). | -| `staging_ttl_days` | int | `7` | Days to retain `final_model.staging./` after a `forgelm reject` decision before scheduled cleanup. Set to `0` to retain indefinitely. Replaces the deprecated `evaluation.staging_ttl_days`; both keys accepted with identical values during the deprecation window (legacy key removed in v0.8.0). | +| `staging_ttl_days` | int | `7` | Days to retain `final_model.staging./` after a `forgelm reject` decision before scheduled cleanup. Set to `0` to retain indefinitely. Replaces the removed `evaluation.staging_ttl_days` (removed in v0.8.0). | | `ephemeral_artefact_retention_days` | int | `90` | Days to retain compliance bundles, data audit reports, and other run-scoped derived artefacts. Set to `0` to retain indefinitely. | | `raw_documents_retention_days` | int | `90` | Days to retain ingested raw documents (PDF / DOCX / EPUB / TXT / Markdown) under the operator's ingestion-output directory. Set to `0` to retain indefinitely. | | `enforce` | string | `"log_only"` | Policy enforcement mode: `"log_only"` (audit-log only), `"warn_on_excess"` (structured stderr warning), `"block_on_excess"` (abort trainer pre-flight with `EXIT_EVAL_FAILURE` = 3). | -> **Deprecation:** `evaluation.staging_ttl_days` is deprecated as of v0.5.5 in -> favour of `retention.staging_ttl_days`. The legacy key is alias-forwarded -> with a `DeprecationWarning` until its removal in v0.8.0. See -> [release.md](../standards/release.md#deprecation-cadence) for the full -> deprecation cadence policy. +> **Removed:** `evaluation.staging_ttl_days` (deprecated as of v0.5.5) was +> removed in v0.8.0. `retention.staging_ttl_days` is now the only accepted form. +> YAML files that still set the legacy key will fail config-load with `EXIT_CONFIG_ERROR`. --- diff --git a/docs/reference/soc2_trust_criteria_mapping-tr.md b/docs/reference/soc2_trust_criteria_mapping-tr.md index 0466c906..74e7160f 100644 --- a/docs/reference/soc2_trust_criteria_mapping-tr.md +++ b/docs/reference/soc2_trust_criteria_mapping-tr.md @@ -96,7 +96,7 @@ Güçlü ForgeLM katkısı. | P2.1 Seçim ve onay | `evaluation.require_human_approval` Madde 14 gate | | P3.1 Toplama | `data.governance.personal_data_included`; `dpia_completed` | | P3.2 Kişisel veri kalitesi | `data_audit_report.json` quality stats | -| P4.1 Kullanım, saklama ve imha | `retention.staging_ttl_days` (kanonik; eski takma ad `evaluation.staging_ttl_days` v0.5.5 → v0.6.x deprecation penceresi boyunca şeffaf yönlendirir); `forgelm purge --check-policy` | +| P4.1 Kullanım, saklama ve imha | `retention.staging_ttl_days` (kanonik; eski takma ad `evaluation.staging_ttl_days` v0.8.0'da kaldırıldı); `forgelm purge --check-policy` | | P5.1 Erişim | `forgelm reverse-pii` Madde 15 scan; salted query-hash | | P5.2 Sorular ve şikayetler | (Operatör-tarafı workflow) | | P6.1 Üçüncü taraflara açıklama | `safe_post` webhook discipline (yalnız TLS, SSRF koruması, yönlendirme reddi); not: webhook gövdeleri HMAC ile imzalanmaz — bkz. ISO 27001 A.8.21 | diff --git a/docs/reference/soc2_trust_criteria_mapping.md b/docs/reference/soc2_trust_criteria_mapping.md index 2ccb163c..dfb2da5f 100644 --- a/docs/reference/soc2_trust_criteria_mapping.md +++ b/docs/reference/soc2_trust_criteria_mapping.md @@ -97,7 +97,7 @@ Strong ForgeLM contribution. | P2.1 Choice and consent | `evaluation.require_human_approval` Article 14 gate | | P3.1 Collection | `data.governance.personal_data_included`; `dpia_completed` | | P3.2 Quality of personal data | `data_audit_report.json` quality stats | -| P4.1 Use, retention, and disposal | `retention.staging_ttl_days` (canonical; legacy alias `evaluation.staging_ttl_days` forwards transparently during the v0.5.5 → v0.6.x deprecation window); `forgelm purge --check-policy` | +| P4.1 Use, retention, and disposal | `retention.staging_ttl_days` (canonical; legacy alias `evaluation.staging_ttl_days` was removed in v0.8.0); `forgelm purge --check-policy` | | P5.1 Access | `forgelm reverse-pii` Article 15 scan; salted query-hash | | P5.2 Inquiries and complaints | (Deployer-side workflow) | | P6.1 Disclosure to third parties | `safe_post` webhook discipline (TLS-only, SSRF guard, redirect refusal); note: webhook bodies are NOT HMAC-signed — see ISO 27001 A.8.21 | diff --git a/docs/reference/usage-tr.md b/docs/reference/usage-tr.md index 5d6dc4bd..a4065f73 100644 --- a/docs/reference/usage-tr.md +++ b/docs/reference/usage-tr.md @@ -223,9 +223,6 @@ forgelm audit data/ --output ./audit/ --pii-ml # stdout'a makine-okunabilir özet forgelm audit data/sft.jsonl --output ./audit/ --output-format json - -# Eski alias (çalışmaya devam ediyor; bir uyarı log'lanır) -forgelm --data-audit data/sft.jsonl --output ./audit/ ``` Denetim şunları yakalar: split başına örnek sayısı + uzunluk dağılımı, top-3 dil tespiti, **LSH-banded** simhash near-duplicate oranı (uç eşiklerde brute-force fallback; opsiyonel **MinHash LSH** yolu `--dedup-method minhash` ile), cross-split sızıntı (sessiz train-test örtüşmesi), PII flag sayıları + **şiddet katmanları** (`pii_severity` bloğu her PII tipini critical / high / medium / low olarak puanlar ve bir `worst_tier` verdict yüzdürür), her zaman açık **credentials/secrets taraması** (`secrets_summary` — `forgelm.data_audit.SECRET_TYPES`'ta tanımlı dokuz aile: `aws_access_key`, `github_token`, `slack_token`, `openai_api_key`, `google_api_key`, `jwt`, `openssh_private_key` (tam BEGIN…END bloğu), `pgp_private_key` (tam BEGIN…END bloğu), `azure_storage_key`), ve opt-in **heuristik kalite filtresi** (`--quality-filter`, `quality_summary` bloğu üretir). diff --git a/docs/reference/usage.md b/docs/reference/usage.md index 0a4bbfd3..fa14eb7c 100644 --- a/docs/reference/usage.md +++ b/docs/reference/usage.md @@ -238,9 +238,6 @@ forgelm audit data/ --output ./audit/ --pii-ml # Machine-readable summary on stdout forgelm audit data/sft.jsonl --output ./audit/ --output-format json - -# Legacy alias (kept working; logs a one-line deprecation notice) -forgelm --data-audit data/sft.jsonl --output ./audit/ ``` The audit captures: per-split sample count + length distribution, top-3 language detection, **LSH-banded** simhash near-duplicate rate (brute-force fallback at edge thresholds; optional **MinHash LSH** path via `--dedup-method minhash`), cross-split leakage (silent train-test overlap), PII flag counts with **severity tiers**, **always-on credentials/secrets scan** (`secrets_summary` — nine families per `forgelm.data_audit.SECRET_TYPES`: `aws_access_key`, `github_token`, `slack_token`, `openai_api_key`, `google_api_key`, `jwt`, `openssh_private_key` (full BEGIN…END block), `pgp_private_key` (full BEGIN…END block), `azure_storage_key`), and an opt-in **heuristic quality filter** (Gopher/C4 style) that adds a `quality_summary` block. diff --git a/docs/usermanuals/en/reference/cli.md b/docs/usermanuals/en/reference/cli.md index b05b1d82..1afcb569 100644 --- a/docs/usermanuals/en/reference/cli.md +++ b/docs/usermanuals/en/reference/cli.md @@ -49,8 +49,7 @@ Run `forgelm --help` for any of these. | `--merge` | Run model merging from the `merge:` config block. No training. | | `--generate-data` | Generate synthetic training data using the teacher model. No training. | | `--compliance-export OUTPUT_DIR` | Export EU AI Act compliance artifacts (audit trail, data provenance, Annex IV) to OUTPUT_DIR. Run after training so the manifest is complete. | -| `--data-audit PATH` | **Deprecated alias** for `forgelm audit PATH`. Scheduled for removal in v0.8.0. New scripts should use the subcommand. | -| `--output DIR` | Output directory for `--data-audit` / `--compliance-export` (default: `./audit/` or `./compliance/`). | +| `--output DIR` | Output directory for `--compliance-export` (default: `./compliance/`). | | `--output-format {text,json}` | Output format for results (default: `text`). JSON for CI. | | `--quiet, -q` | Suppress INFO logs. Only show warnings and errors. | | `--log-level {DEBUG,INFO,WARNING,ERROR}` | Set logging verbosity (default: INFO). | diff --git a/docs/usermanuals/tr/reference/cli.md b/docs/usermanuals/tr/reference/cli.md index ecf8243b..647fd37f 100644 --- a/docs/usermanuals/tr/reference/cli.md +++ b/docs/usermanuals/tr/reference/cli.md @@ -49,8 +49,7 @@ Bunlardan herhangi biri için `forgelm --help`. | `--merge` | Config'in `merge:` bloğundan model birleştirmeyi koştur. Eğitim yok. | | `--generate-data` | Teacher modelle sentetik eğitim verisi üret. Eğitim yok. | | `--compliance-export OUTPUT_DIR` | EU AI Act uyum artifact'larını (audit trail, data provenance, Annex IV) OUTPUT_DIR'a export et. Manifest'in tamamlanması için eğitimden sonra koşturun. | -| `--data-audit PATH` | **Deprecated alias**, `forgelm audit PATH` için. v0.8.0'da kaldırılacak. Yeni script'ler subcommand'ı kullanmalı. | -| `--output DIR` | `--data-audit` / `--compliance-export` için çıktı dizini (varsayılan: `./audit/` veya `./compliance/`). | +| `--output DIR` | `--compliance-export` için çıktı dizini (varsayılan: `./compliance/`). | | `--output-format {text,json}` | Sonuçlar için çıktı formatı (varsayılan: `text`). CI için JSON. | | `--quiet, -q` | INFO loglarını bastır. Sadece warning ve error göster. | | `--log-level {DEBUG,INFO,WARNING,ERROR}` | Log detay seviyesi (varsayılan: INFO). | diff --git a/forgelm/_version.py b/forgelm/_version.py index 11ba43ec..51c010a4 100644 --- a/forgelm/_version.py +++ b/forgelm/_version.py @@ -44,8 +44,11 @@ # v0.5.7 fixes a runtime ``SFTConfig.max_seq_length`` TypeError on # modern trl (rename to ``max_length`` in trl 0.13+) plus the Intel # Mac NumPy 2 ABI mismatch (numpy<2 marker + ``numpy.torch_abi`` -# doctor probe). None of these patches change the Python API -# surface, so ``__api_version__`` stays at 1.0.0. -__api_version__ = "1.0.0" +# doctor probe). None of those patches changed the Python API surface, +# so ``__api_version__`` stayed at 1.0.0 through v0.7.0. +# Bumped to 1.1.0 in v0.8.0: the stable ``verify_integrity`` callable and +# ``VerifyIntegrityResult`` dataclass were added to ``forgelm.__all__`` +# (additive → MINOR ``__api_version__`` bump per the rule above). +__api_version__ = "1.1.0" __all__ = ["__version__", "__api_version__"] diff --git a/forgelm/cli/_dispatch.py b/forgelm/cli/_dispatch.py index 2b6789ac..e3b80891 100644 --- a/forgelm/cli/_dispatch.py +++ b/forgelm/cli/_dispatch.py @@ -9,7 +9,6 @@ import json import sys -import warnings from ._config_load import _apply_offline_flag, _load_config_or_exit from ._exit_codes import EXIT_CONFIG_ERROR, EXIT_SUCCESS, EXIT_TRAINING_ERROR, _clamp_exit_code @@ -170,76 +169,6 @@ def main(): sys.exit(EXIT_TRAINING_ERROR) -def _dispatch_legacy_data_audit(args) -> None: - """Handle ``forgelm --data-audit PATH`` (deprecated flag). - - Pulled out of ``_main_inner`` for Sonar python:S3776 cognitive- - complexity hygiene. Emits the standard deprecation - ``DeprecationWarning`` + an audit-log breadcrumb (best-effort), then - delegates to the canonical ``_run_data_audit`` and ``sys.exit``s. - Calls back to ``_main_inner`` are impossible — this function always - terminates the process. - """ - json_output = args.output_format == "json" - log_level = "WARNING" if args.quiet else args.log_level - _setup_logging(log_level, json_format=json_output) - # Phase 13 (Faz 13): emit a structured Python ``DeprecationWarning`` - # so `pytest -W error::DeprecationWarning` and `python -Wd` tooling - # surface it, plus an append-only audit-log event so operators who - # only read the JSONL trail see the migration signal too. Cadence - # for the v0.8.0 removal follows the "Deprecation cadence" section - # in ``docs/standards/release.md`` (one-minor warning window - # minimum). Originally targeted v0.7.0; pushed one minor out to - # v0.8.0 at the v0.7.0 cut to preserve the one-minor warning - # window for operators who only upgrade once per minor release. - warnings.warn( - "`forgelm --data-audit PATH` is deprecated and will be removed " - "in v0.8.0. Use the `forgelm audit PATH` subcommand instead — " - "same behaviour, same output. " - "See docs/standards/release.md#deprecation-cadence for the removal timeline.", - DeprecationWarning, - stacklevel=2, - ) - # Audit-log event: append-only Article 12 record of the legacy - # invocation so compliance reviewers can prove the operator was - # warned. Best-effort — a read-only output dir must not abort the - # actual audit run. - legacy_target = args.output or "./audit" - try: - from ..compliance import AuditLogger - from ..config import ConfigError - - AuditLogger(legacy_target).log_event( - "cli.legacy_flag_invoked", - flag="--data-audit", - replacement="forgelm audit", - version="v0.8.0 removal", - ) - except (OSError, ConfigError) as audit_exc: - logger.warning( - "Failed to emit deprecation audit event to %s: %s", - legacy_target, - audit_exc, - ) - # Late import via the package facade so monkeypatched - # ``forgelm.cli._run_data_audit`` references resolve correctly. - from forgelm import cli as _cli_facade - - # The deprecation warning, the parser help, and the helper docstring all - # promise "same behaviour, same output" as `forgelm audit PATH`. The - # subcommand defaults --quality-filter ON (v0.6.0+), so the legacy alias - # must pass enable_quality_filter=True to keep that promise — otherwise it - # falls back to the helper's opt-in default (False) and emits a - # quality_summary-less data_audit_report.json (F-P7-OPUS-01). - _cli_facade._run_data_audit( - args.data_audit, - args.output, - args.output_format, - enable_quality_filter=True, - ) - sys.exit(EXIT_SUCCESS) - - def _dispatch_pipeline_mode(config, args) -> None: """Handle a config that carries a ``pipeline:`` block. @@ -285,17 +214,8 @@ def _main_inner() -> None: _dispatch_subcommand(command, args) # --data-audit operates on a JSONL file/directory only — no config needed. - # Run before the config-required check so operators can audit raw data - # without writing a YAML. Phase 11.5 promoted the same code path to - # `forgelm audit PATH` (a real subcommand); the legacy flag is preserved - # as an alias and slated for removal in v0.8.0. (Removal originally - # targeted v0.7.0; pushed one minor out at the v0.7.0 cut to preserve - # the documented one-minor warning window — see the matching - # DeprecationWarning message + audit-event ``version`` field below - # and ``docs/standards/release.md#deprecation-cadence``.) - if getattr(args, "data_audit", None): - _dispatch_legacy_data_audit(args) - + # (The legacy ``forgelm --data-audit PATH`` flag was removed in v0.8.0; + # use the first-class ``forgelm audit PATH`` subcommand instead.) _maybe_run_wizard(args) if not args.config: diff --git a/forgelm/cli/_parser.py b/forgelm/cli/_parser.py index e3aefb50..bf09b96b 100644 --- a/forgelm/cli/_parser.py +++ b/forgelm/cli/_parser.py @@ -481,8 +481,8 @@ def _add_audit_subcommand(subparsers) -> None: "audit", help="Run a Phase 11/11.5/12 dataset audit on a JSONL file or split-keyed directory.", description=( - "Phase 11.5: first-class audit subcommand (the legacy `--data-audit FLAG` is preserved " - "as a deprecation alias). Computes per-split length distribution, top-3 language detection, " + "Phase 11.5: first-class audit subcommand (the legacy `--data-audit FLAG` was removed in " + "v0.8.0). Computes per-split length distribution, top-3 language detection, " "near-duplicate detection (LSH-banded), cross-split overlap, and PII flags with severity " "tiers — feeding the EU AI Act Article 10 governance artifact when run inside a training " "output directory. Phase 12 added: --dedup-method minhash for MinHash LSH, --quality-filter " @@ -1355,24 +1355,12 @@ def parse_args(): "standalone use produces artifacts with empty metrics." ), ) - parser.add_argument( - "--data-audit", - type=str, - default=None, - metavar="PATH", - help=( - "DEPRECATED — alias for `forgelm audit PATH` (kept so existing pipelines keep " - "working). Scheduled for removal in v0.8.0. Behaviour is identical; new scripts " - "should use the subcommand. Writes `data_audit_report.json` under --output " - "(default ./audit/). No training." - ), - ) parser.add_argument( "--output", type=str, default=None, metavar="DIR", - help="Output directory for --data-audit / --compliance-export (default: ./audit or ./compliance).", + help="Output directory for --compliance-export (default: ./compliance).", ) parser.add_argument( "-q", diff --git a/forgelm/cli/subcommands/_audit.py b/forgelm/cli/subcommands/_audit.py index 466705a2..0b6580fa 100644 --- a/forgelm/cli/subcommands/_audit.py +++ b/forgelm/cli/subcommands/_audit.py @@ -1,8 +1,8 @@ -"""``forgelm audit`` dispatcher + the legacy ``--data-audit`` worker. +"""``forgelm audit`` dispatcher + the shared dataset-audit worker. -The legacy ``forgelm --data-audit PATH`` flag is still routed here from -``main()`` (with a deprecation warning); both entry points share the same -underlying audit code so behaviour stays identical. +``_run_data_audit`` is the underlying worker behind the ``forgelm audit`` +subcommand. (The legacy ``forgelm --data-audit PATH`` flag that previously +shared this code path was removed in v0.8.0.) """ from __future__ import annotations @@ -33,11 +33,9 @@ def _run_data_audit( ) -> None: """Phase 11 / 11.5 / 12 dispatch: dataset quality + governance audit. - The behaviour is identical whether the operator reaches us via the - Phase 11.5 ``forgelm audit`` subcommand or the legacy ``--data-audit`` - flag, so existing CI pipelines keep working unchanged. Phase 13 moved - the legacy-flag deprecation notice + audit-log event up to the - dispatch site in :func:`main` so this helper stays single-purpose. + Reached via the ``forgelm audit`` subcommand. (The legacy + ``--data-audit`` flag that previously shared this worker was removed in + v0.8.0; this helper stays single-purpose.) """ from ...data_audit import ( DEFAULT_MINHASH_JACCARD, diff --git a/forgelm/compliance.py b/forgelm/compliance.py index 08fdd8d6..71da88c0 100644 --- a/forgelm/compliance.py +++ b/forgelm/compliance.py @@ -507,7 +507,7 @@ def generate_data_governance_report(config: Any, dataset: Dict[str, Any]) -> Dic """Generate data quality and governance report per EU AI Act Article 10. When an audit report (``data_audit_report.json``) was produced by - ``forgelm --data-audit`` and lives in the trainer's checkpoint dir, + ``forgelm audit`` and lives in the trainer's checkpoint dir, its findings are inlined under the ``data_audit`` key so the governance artifact is a single self-contained document rather than a pointer. diff --git a/forgelm/config.py b/forgelm/config.py index 39b2bf05..eabde60e 100644 --- a/forgelm/config.py +++ b/forgelm/config.py @@ -826,22 +826,9 @@ class EvaluationConfig(BaseModel): default=False, description="Article 14: pause the pipeline for human review (stages model under `final_model.staging./` and exits 4).", ) - # ``final_model.staging./`` retention horizon for `forgelm reject` paths. - # Documented now (v0.5.5) so operators can plan their evidence-preservation - # policy; auto-deletion enforcement is deferred to Phase 21 (GDPR - # right-to-erasure) where it lands alongside the broader retention - # framework. Setting the value today has no runtime effect — it is - # surfaced in the compliance manifest so reviewers can audit the policy. - staging_ttl_days: int = Field( - default=7, - ge=0, - description=( - "Article 14: number of days to retain `final_model.staging./` after a " - "`forgelm reject` decision before scheduled cleanup. Zero means retain " - "indefinitely. Auto-deletion enforcement is deferred to Phase 21 " - "(GDPR right-to-erasure)." - ), - ) + # NOTE: the legacy ``evaluation.staging_ttl_days`` field was removed in + # v0.8.0 (deprecated in v0.7.0). The staging-retention horizon now lives + # solely on the canonical ``retention.staging_ttl_days`` block. # EU AI Act risk taxonomy — single source of truth shared by @@ -1149,8 +1136,8 @@ class RetentionConfig(BaseModel): ge=0, description=( "Days to retain `final_model.staging./` after a `forgelm reject` decision before scheduled cleanup. " - "Set to 0 to retain indefinitely. Replaces (and supersedes) the deprecated " - "`evaluation.staging_ttl_days`; both fields are accepted with identical values during the deprecation window (legacy field removed in v0.8.0)." + "Set to 0 to retain indefinitely. This is the canonical staging-retention field; " + "the legacy `evaluation.staging_ttl_days` alias was removed in v0.8.0." ), ) ephemeral_artefact_retention_days: int = Field( @@ -1494,138 +1481,8 @@ def _validate_consistency(self): # bespoke `_validate_trainer_type` runtime check became redundant. self._validate_galore() self._validate_distributed() - self._reconcile_staging_ttl_days() return self - def _reconcile_staging_ttl_days(self) -> None: - """Phase 21 deprecation cadence: reconcile the legacy - ``evaluation.staging_ttl_days`` against the canonical - ``retention.staging_ttl_days``. - - Per Phase 20 design §3.1 v2 (and gdpr-erasure-design L75-81): - - - When **only** ``evaluation.staging_ttl_days`` is set → - alias-forward to ``retention.staging_ttl_days`` (creating - ``retention`` block if missing) and emit a single - ``DeprecationWarning`` naming the new field + the v0.8.0 - removal target. - - When **only** ``retention.staging_ttl_days`` is set → no - warning; canonical path. - - When **both** are set with **identical** values → emit - ``DeprecationWarning`` for the deprecated field; the canonical - ``retention.staging_ttl_days`` value wins; operator's intent - is unambiguous. - - When **both** are set with **different** values → raise - ``ConfigError`` at validation time naming both keys, both - values, and instructing the operator to remove the deprecated - entry. Silent winner = wrong winner. - - Wave 2b Round-4 review F-W2B-02 fix: Pydantic v2 exposes - ``model_fields_set`` exactly to distinguish "operator wrote - the field in YAML" from "Pydantic filled the default". We - consult that set so an operator who follows the documented - deprecation cadence (delete the deprecated key, add the - canonical block) is not refused with ``ConfigError`` because - the deprecated default-7 was re-filled. The previous - "value differs from default" heuristic mis-handled the - explicit-default + canonical-different scenario. - """ - # Bind the optional sub-models locally so the type narrowing is - # visible to static analysers (SonarCloud S2259) and the field- - # explicitness checks below cannot race against another mutator. - evaluation = self.evaluation - retention = self.retention - - legacy_was_explicitly_set = bool(evaluation is not None and "staging_ttl_days" in evaluation.model_fields_set) - legacy = evaluation.staging_ttl_days if (legacy_was_explicitly_set and evaluation is not None) else None - # Wave 2b Round-5 review F-W2B-RETENTION: applying the same - # ``model_fields_set`` test to the canonical block. An operator - # who writes ``retention: {audit_log_retention_days: 1825}`` - # (no staging key) leaves ``staging_ttl_days`` at its default - # of 7; treating that 7 as an explicit canonical value would - # spuriously raise ``ConfigError`` when paired with - # ``evaluation.staging_ttl_days: 14``. We only treat - # ``retention.staging_ttl_days`` as canonical when the operator - # actually wrote it. - canonical_was_explicitly_set = bool(retention is not None and "staging_ttl_days" in retention.model_fields_set) - canonical = retention.staging_ttl_days if (canonical_was_explicitly_set and retention is not None) else None - - # Both unset → nothing to do. - if legacy is None and canonical is None: - return - # Only canonical set (or operator deleted the deprecated key) → - # canonical path; no warning. - if legacy is None and canonical is not None: - return - # Only legacy set explicitly → alias-forward. - if legacy is not None and canonical is None: - self._apply_legacy_alias_forward(legacy, retention) - return - # Both set. Compare. - if legacy == canonical: - self._emit_legacy_match_warning() - return - # Both set with different values → refuse. - raise ConfigError( - "Conflicting staging_ttl_days values: " - f"`evaluation.staging_ttl_days={legacy}` (deprecated, forwards to " - f"`retention.staging_ttl_days`) vs `retention.staging_ttl_days={canonical}` " - "(canonical). Remove the deprecated entry; the canonical block wins. " - "(Tracking issue: removal scheduled for v0.8.0 per " - "docs/standards/release.md#deprecation-cadence.)" - ) - - def _apply_legacy_alias_forward(self, legacy: int, retention: Optional["RetentionConfig"]) -> None: - """Mirror ``evaluation.staging_ttl_days`` onto ``retention.staging_ttl_days``. - - ``model_copy(update=...)`` preserves any other ``retention.*`` keys - the operator already wrote (e.g. ``audit_log_retention_days: 1825`` - paired with ``evaluation.staging_ttl_days: 14``). The previous - ``RetentionConfig(staging_ttl_days=legacy)`` constructor call would - have silently discarded those. - - ``stacklevel=5`` is tuned so the DeprecationWarning surfaces at the - operator's ``ForgeConfig(...)`` call site rather than inside the - Pydantic ``@model_validator`` machinery (caller → - ``_reconcile_staging_ttl_days`` → here). - """ - if retention is not None: - self.retention = retention.model_copy(update={"staging_ttl_days": legacy}) - else: - self.retention = RetentionConfig(staging_ttl_days=legacy) - # Pair the DeprecationWarning with a logger.warning: CPython's default - # filters suppress DeprecationWarning emitted outside __main__, so the - # warnings.warn call alone never reaches a CLI operator (F-P1-FAB-17). - # The logger line mirrors the lora.use_dora / sample_packing idiom and - # surfaces on the CLI path; the warnings.warn keeps `-W error` / CI - # deprecation sweeps working for library consumers. - message = ( - "`evaluation.staging_ttl_days` is deprecated and forwards to " - "`retention.staging_ttl_days`. " - "Move the value under the new top-level `retention:` block; the " - "deprecated field is removed in v0.8.0." - ) - logger.warning(message) - warnings.warn(message, DeprecationWarning, stacklevel=5) - - def _emit_legacy_match_warning(self) -> None: - """Warn when both fields are set to identical values; canonical wins. - - ``stacklevel=5`` matches :meth:`_apply_legacy_alias_forward` so both - deprecation paths attribute the warning to the same operator - call frame. - """ - # See `_apply_legacy_alias_forward`: pair the logger.warning so the - # deprecation reaches CLI operators, not just `-W error` consumers. - message = ( - "`evaluation.staging_ttl_days` is deprecated; the value matches " - "`retention.staging_ttl_days` so the canonical block wins. Remove " - "`evaluation.staging_ttl_days` from your YAML — the deprecated field " - "is removed in v0.8.0." - ) - logger.warning(message) - warnings.warn(message, DeprecationWarning, stacklevel=5) - class ConfigError(Exception): """Raised when configuration validation fails.""" @@ -1853,21 +1710,18 @@ def merge_pipeline_stage_config( """ # ``exclude_unset=True`` so only keys the operator actually wrote # round-trip — re-validation re-fills defaults identically. Without it, - # ``model_dump`` materialises *unset* defaults (e.g. an ``evaluation`` - # block dumps ``staging_ttl_days=7``); on re-validation every dumped key - # counts as ``model_fields_set``, so a root with a canonical - # ``retention.staging_ttl_days != 7`` plus any ``evaluation`` block would - # falsely raise ``ConfigError`` ("conflicting staging_ttl_days") for a - # field the operator never wrote (F-P1-FAB-03). ``exclude_none=True`` - # additionally drops inherited ``Optional[T] = None`` fields so the - # per-stage manifest is not inflated with no-op None values. + # ``model_dump`` materialises *unset* defaults; on re-validation every + # dumped key counts as ``model_fields_set``, which can spuriously trip + # cross-field validators that fire only on operator-written values + # (F-P1-FAB-03). ``exclude_none=True`` additionally drops inherited + # ``Optional[T] = None`` fields so the per-stage manifest is not inflated + # with no-op None values. base = root_cfg.model_dump(exclude_none=True, exclude_unset=True) base.pop("pipeline", None) # Stage overrides use the same ``exclude_unset=True`` rationale as the root - # dump above: a stage ``evaluation`` block that omits ``staging_ttl_days`` - # must not materialise the default ``7`` and falsely conflict with a - # canonical ``retention.staging_ttl_days`` on re-validation (F-P1-FAB-03). + # dump above: a stage block that omits a field must not materialise that + # field's default as though the operator had written it (F-P1-FAB-03). if stage.model is not None: base["model"] = stage.model.model_dump(exclude_none=True, exclude_unset=True) if stage.lora is not None: diff --git a/notebooks/data_curation.ipynb b/notebooks/data_curation.ipynb index 04d5e35c..88b34e2f 100644 --- a/notebooks/data_curation.ipynb +++ b/notebooks/data_curation.ipynb @@ -24,7 +24,7 @@ "source": [ "# Step 1: Install ForgeLM with the ingestion extra (PDF/DOCX/EPUB/Markdown parsers + langdetect)\n", "# Pinned to v0.7.0; bump on each release.\n", - "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.7.0'\n", + "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.8.0'\n", "!forgelm --version" ] }, @@ -341,7 +341,7 @@ "# into ``forgelm.data_audit._require_datasketch`` matches the recipe used\n", "# here. This pulls ``datasketch>=1.6.0,<2.0.0`` along with any future\n", "# scale-tier deps the extra absorbs.\n", - "!pip install -q 'forgelm[ingestion-scale]==0.7.0'\n", + "!pip install -q 'forgelm[ingestion-scale]==0.8.0'\n", "!forgelm audit ./curated/policies.jsonl \\\n", " --output ./audit_minhash/ \\\n", " --dedup-method minhash \\\n", diff --git a/notebooks/dpo_alignment.ipynb b/notebooks/dpo_alignment.ipynb index 76c2880c..9f635852 100644 --- a/notebooks/dpo_alignment.ipynb +++ b/notebooks/dpo_alignment.ipynb @@ -21,7 +21,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", "# Upgrade so the comparison cell at the end can load the trained LoRA adapter.\n", diff --git a/notebooks/galore_memory_optimization.ipynb b/notebooks/galore_memory_optimization.ipynb index 241f4587..c938a826 100644 --- a/notebooks/galore_memory_optimization.ipynb +++ b/notebooks/galore_memory_optimization.ipynb @@ -44,7 +44,7 @@ "source": [ "# Step 1: Install ForgeLM + GaLore\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip install -q galore-torch # GaLore optimizer\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", diff --git a/notebooks/grpo_reasoning.ipynb b/notebooks/grpo_reasoning.ipynb index 793b9ef8..edf0bc63 100644 --- a/notebooks/grpo_reasoning.ipynb +++ b/notebooks/grpo_reasoning.ipynb @@ -21,7 +21,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", "# Upgrade so the comparison cell at the end can load the trained LoRA adapter.\n", diff --git a/notebooks/ingestion_playground.ipynb b/notebooks/ingestion_playground.ipynb index 830eafc7..502f579a 100644 --- a/notebooks/ingestion_playground.ipynb +++ b/notebooks/ingestion_playground.ipynb @@ -29,7 +29,7 @@ "source": [ "# Step 1: Install ForgeLM with the ingestion extra (PDF/DOCX/EPUB parsers + langdetect)\n", "# Pinned to v0.7.0; bump on each release.\n", - "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.7.0'\n", + "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.8.0'\n", "!forgelm --version" ] }, diff --git a/notebooks/kto_binary_feedback.ipynb b/notebooks/kto_binary_feedback.ipynb index 5a53885d..4dbabc3f 100644 --- a/notebooks/kto_binary_feedback.ipynb +++ b/notebooks/kto_binary_feedback.ipynb @@ -21,7 +21,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", "# Upgrade so the comparison cell at the end can load the trained LoRA adapter.\n", diff --git a/notebooks/multi_dataset.ipynb b/notebooks/multi_dataset.ipynb index e8e07a0e..51e80df0 100644 --- a/notebooks/multi_dataset.ipynb +++ b/notebooks/multi_dataset.ipynb @@ -21,7 +21,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", "# Upgrade so the comparison cell at the end can load the trained LoRA adapter.\n", diff --git a/notebooks/post_training_workflow.ipynb b/notebooks/post_training_workflow.ipynb index 8ea398e2..f0ce1405 100644 --- a/notebooks/post_training_workflow.ipynb +++ b/notebooks/post_training_workflow.ipynb @@ -36,11 +36,11 @@ "outputs": [], "source": [ "# Install ForgeLM (skip on Colab if quickstart_sft already ran in this session)\n", - "!pip install -q --no-cache-dir 'forgelm==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm==0.8.0' bitsandbytes\n", "# Download config_template.yaml from the v0.7.0 tag (NOT main) so the YAML\n", "# matches the wheel above — `main` drifts as features land and would otherwise\n", "# reference fields the installed Pydantic schema rejects.\n", - "!wget -q https://raw.githubusercontent.com/HodeTech/ForgeLM/v0.7.0/config_template.yaml\n", + "!wget -q https://raw.githubusercontent.com/HodeTech/ForgeLM/v0.8.0/config_template.yaml\n", "!forgelm --version" ] }, @@ -216,7 +216,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -q 'forgelm[export]==0.7.0' # adds llama-cpp-python>=0.2.90" + "!pip install -q 'forgelm[export]==0.8.0' # adds llama-cpp-python>=0.2.90" ] }, { diff --git a/notebooks/quickstart_sft.ipynb b/notebooks/quickstart_sft.ipynb index 8235bc09..933d6d88 100644 --- a/notebooks/quickstart_sft.ipynb +++ b/notebooks/quickstart_sft.ipynb @@ -27,7 +27,7 @@ "source": [ "# Step 1: Install ForgeLM (the [qlora] extra pulls in bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0'\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0'\n", "!pip uninstall -y wandb -q # Remove conflicting wandb (not needed)\n", "\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", diff --git a/notebooks/safety_evaluation.ipynb b/notebooks/safety_evaluation.ipynb index 364b07a5..3b50837b 100644 --- a/notebooks/safety_evaluation.ipynb +++ b/notebooks/safety_evaluation.ipynb @@ -23,7 +23,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "!forgelm --version" ] diff --git a/notebooks/synthetic_data_training.ipynb b/notebooks/synthetic_data_training.ipynb index 6c5c97b1..b1c5b972 100644 --- a/notebooks/synthetic_data_training.ipynb +++ b/notebooks/synthetic_data_training.ipynb @@ -26,7 +26,7 @@ "source": [ "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n", "# Pinned to v0.7.0; bump on each release\n", - "!pip install -q --no-cache-dir 'forgelm[qlora]==0.7.0' bitsandbytes\n", + "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n", "!pip uninstall -y wandb -q\n", "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n", "# Upgrade so the comparison cell at the end can load the trained LoRA adapter.\n", diff --git a/pyproject.toml b/pyproject.toml index b278eae1..6539a5d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta" [project] name = "forgelm" -version = "0.7.1rc1" +version = "0.8.0" description = "Config-driven LLM fine-tuning with safety evaluation, EU AI Act compliance, 6 alignment methods, and one-command bundled quickstart templates." readme = "README.md" requires-python = ">=3.10" diff --git a/site/compliance.html b/site/compliance.html index ed5296df..73cd4283 100644 --- a/site/compliance.html +++ b/site/compliance.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", diff --git a/site/contact.html b/site/contact.html index a069e78e..8033500b 100644 --- a/site/contact.html +++ b/site/contact.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", diff --git a/site/features.html b/site/features.html index 2aa612d3..feb45b8e 100644 --- a/site/features.html +++ b/site/features.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", diff --git a/site/guide.html b/site/guide.html index 5dc9ccc1..38c02684 100644 --- a/site/guide.html +++ b/site/guide.html @@ -54,7 +54,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", diff --git a/site/index.html b/site/index.html index e58d064e..af8cdeae 100644 --- a/site/index.html +++ b/site/index.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", @@ -144,7 +144,7 @@

ForgeLM — Config-driven LLM fi
- Open source · Apache 2.0 · v0.7.0 + Open source · Apache 2.0 · v0.8.0
diff --git a/site/js/translations.js b/site/js/translations.js index ed4e376b..55714c58 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -309,7 +309,7 @@ "meta.title.home": "ForgeLM — Config-driven LLM fine-tuning toolkit", "home.page_title": "ForgeLM — Config-driven LLM fine-tuning toolkit", "meta.desc.home": "YAML-driven LLM fine-tuning with safety eval, EU AI Act compliance, and CI/CD-integrated design — for teams shipping into regulated environments.", - "home.hero.badge": "Open source · Apache 2.0 · v0.7.0", + "home.hero.badge": "Open source · Apache 2.0 · v0.8.0", "home.hero.title": "YAML in.
Fine-tuned model out.", "home.hero.lead": "ForgeLM is the config-driven LLM fine-tuning toolkit for teams that ship into regulated environments. The full alignment stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — with integrated safety evaluation, EU AI Act audit trails, and auto-revert on quality regression.", "home.hero.cta1": "Get started", @@ -415,7 +415,7 @@ "meta.title.home": "ForgeLM — Konfigürasyon odaklı LLM fine-tuning araç seti", "home.page_title": "ForgeLM — YAML temelli LLM fine-tuning araç seti", "meta.desc.home": "YAML temelli LLM fine-tuning — güvenlik değerlendirmesi, EU AI Act uyumu ve CI/CD entegrasyonuyla regülasyonlu ortamlara üretim çıkışı için.", - "home.hero.badge": "Açık kaynak · Apache 2.0 · v0.7.0", + "home.hero.badge": "Açık kaynak · Apache 2.0 · v0.8.0", "home.hero.title": "YAML gir.
Fine-tuned model al.", "home.hero.lead": "ForgeLM, regülasyonlu ortamlara üretim çıkaran ekipler için konfigürasyon odaklı LLM fine-tuning araç setidir. Tam alignment yığını — SFT, DPO, SimPO, KTO, ORPO, GRPO — entegre güvenlik değerlendirmesi, EU AI Act denetim kayıtları ve kalite regresyonunda otomatik geri alma ile.", "home.hero.cta1": "Hemen başla", @@ -521,7 +521,7 @@ "meta.title.home": "ForgeLM — Konfigurationsgesteuertes LLM-Fine-Tuning-Toolkit", "home.page_title": "ForgeLM — Konfigurations­gesteuertes LLM-Fine-Tuning-Toolkit", "meta.desc.home": "YAML-gesteuertes LLM-Fine-Tuning — mit Safety-Evaluation, EU-AI-Act-Compliance und CI/CD-Integration für regulierte Umgebungen.", - "home.hero.badge": "Open Source · Apache 2.0 · v0.7.0", + "home.hero.badge": "Open Source · Apache 2.0 · v0.8.0", "home.hero.title": "YAML rein.
Fine-Tuned Modell raus.", "home.hero.lead": "ForgeLM ist das konfigurationsgesteuerte LLM-Fine-Tuning-Toolkit für Teams, die in regulierte Umgebungen liefern. Der vollständige Alignment-Stack — SFT, DPO, SimPO, KTO, ORPO, GRPO — mit integrierter Sicherheitsbewertung, EU-AI-Act-Audit-Trails und automatischem Rollback bei Qualitätsregression.", "home.hero.cta1": "Loslegen", @@ -627,7 +627,7 @@ "meta.title.home": "ForgeLM — Boîte à outils de fine-tuning LLM pilotée par configuration", "home.page_title": "ForgeLM — Boîte à outils de fine-tuning LLM piloté par YAML", "meta.desc.home": "Fine-tuning LLM piloté par YAML — évaluation safety, conformité EU AI Act et intégration CI/CD pour environnements régulés.", - "home.hero.badge": "Open source · Apache 2.0 · v0.7.0", + "home.hero.badge": "Open source · Apache 2.0 · v0.8.0", "home.hero.title": "Du YAML.
Un modèle fine-tuné.", "home.hero.lead": "ForgeLM est la boîte à outils de fine-tuning LLM pilotée par configuration pour les équipes qui livrent en environnement régulé. La pile d'alignement complète — SFT, DPO, SimPO, KTO, ORPO, GRPO — avec évaluation de sécurité intégrée, pistes d'audit EU AI Act et rollback automatique en cas de régression de qualité.", "home.hero.cta1": "Commencer", @@ -733,7 +733,7 @@ "meta.title.home": "ForgeLM — Toolkit de fine-tuning de LLM basado en configuración", "home.page_title": "ForgeLM — Toolkit de fine-tuning de LLM dirigido por YAML", "meta.desc.home": "Fine-tuning de LLM dirigido por YAML — con evaluación de safety, cumplimiento EU AI Act e integración CI/CD para entornos regulados.", - "home.hero.badge": "Código abierto · Apache 2.0 · v0.7.0", + "home.hero.badge": "Código abierto · Apache 2.0 · v0.8.0", "home.hero.title": "YAML entra.
Modelo fine-tuned sale.", "home.hero.lead": "ForgeLM es el toolkit de fine-tuning de LLM basado en configuración para equipos que despliegan en entornos regulados. El stack de alignment completo — SFT, DPO, SimPO, KTO, ORPO, GRPO — con evaluación de seguridad integrada, registros de auditoría EU AI Act y reversión automática ante regresiones de calidad.", "home.hero.cta1": "Empezar", @@ -839,7 +839,7 @@ "meta.title.home": "ForgeLM — 基于配置的 LLM 微调工具包", "home.page_title": "ForgeLM — YAML 驱动的 LLM 微调工具包", "meta.desc.home": "YAML 驱动的 LLM 微调 — 集成安全评估、EU AI Act 合规和 CI/CD,面向受监管环境部署。", - "home.hero.badge": "开源 · Apache 2.0 · v0.7.0", + "home.hero.badge": "开源 · Apache 2.0 · v0.8.0", "home.hero.title": "输入 YAML。
输出微调模型。", "home.hero.lead": "ForgeLM 是面向需要在受监管环境中交付的团队的、基于配置的 LLM 微调工具包。完整对齐栈 — SFT、DPO、SimPO、KTO、ORPO、GRPO — 配备内置安全评估、EU AI Act 审计轨迹,以及质量回归时的自动回滚。", "home.hero.cta1": "开始使用", diff --git a/site/privacy.html b/site/privacy.html index da60c4e4..2c50ebe8 100644 --- a/site/privacy.html +++ b/site/privacy.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", diff --git a/site/quickstart.html b/site/quickstart.html index c15d541d..88ba91f4 100644 --- a/site/quickstart.html +++ b/site/quickstart.html @@ -48,7 +48,7 @@ "downloadUrl": "https://pypi.org/project/forgelm/", "operatingSystem": "Linux, macOS, Windows", "license": "https://www.apache.org/licenses/LICENSE-2.0", - "softwareVersion": "0.7.0", + "softwareVersion": "0.8.0", "offers": { "@type": "Offer", "price": "0", @@ -209,12 +209,12 @@

Install

terminal
-
$ pip install forgelm==0.7.0
+            
$ pip install forgelm==0.8.0
 
 # or with extras (pick what you need)
-$ pip install 'forgelm[ingestion]==0.7.0'          # PDF/DOCX/EPUB ingest
-$ pip install 'forgelm[ingestion-scale]==0.7.0'    # MinHash LSH for >50K rows
-$ pip install 'forgelm[export]==0.7.0'             # GGUF export
+$ pip install 'forgelm[ingestion]==0.8.0'          # PDF/DOCX/EPUB ingest
+$ pip install 'forgelm[ingestion-scale]==0.8.0'    # MinHash LSH for >50K rows
+$ pip install 'forgelm[export]==0.8.0'             # GGUF export
 
 # sanity-check your environment before the first run
 $ forgelm doctor
diff --git a/site/terms.html b/site/terms.html
index 02a71ef5..f5df503e 100644
--- a/site/terms.html
+++ b/site/terms.html
@@ -48,7 +48,7 @@
   "downloadUrl": "https://pypi.org/project/forgelm/",
   "operatingSystem": "Linux, macOS, Windows",
   "license": "https://www.apache.org/licenses/LICENSE-2.0",
-  "softwareVersion": "0.7.0",
+  "softwareVersion": "0.8.0",
   "offers": {
     "@type": "Offer",
     "price": "0",
diff --git a/tests/_data/api_signatures_1.1.0.json b/tests/_data/api_signatures_1.1.0.json
new file mode 100644
index 00000000..b21bcffc
--- /dev/null
+++ b/tests/_data/api_signatures_1.1.0.json
@@ -0,0 +1,188 @@
+{
+  "__api_version__": "1.1.0",
+  "symbols": {
+    "AuditLogger": {
+      "kind": "callable",
+      "signature": "(output_dir: str, run_id: Optional[str] = None)"
+    },
+    "AuditReport": {
+      "fields": [
+        "generated_at",
+        "source_path",
+        "source_input",
+        "total_samples",
+        "splits",
+        "cross_split_overlap",
+        "pii_summary",
+        "pii_severity",
+        "near_duplicate_summary",
+        "secrets_summary",
+        "quality_summary",
+        "croissant",
+        "notes"
+      ],
+      "kind": "dataclass"
+    },
+    "BenchmarkResult": {
+      "fields": [
+        "scores",
+        "average_score",
+        "passed",
+        "failure_reason",
+        "raw_results"
+      ],
+      "kind": "dataclass"
+    },
+    "ConfigError": {
+      "kind": "callable",
+      "signature": null
+    },
+    "ForgeConfig": {
+      "fields": [
+        "auth",
+        "compliance",
+        "data",
+        "distributed",
+        "evaluation",
+        "lora",
+        "merge",
+        "model",
+        "monitoring",
+        "pipeline",
+        "retention",
+        "risk_assessment",
+        "synthetic",
+        "training",
+        "webhook"
+      ],
+      "kind": "pydantic_model"
+    },
+    "ForgeTrainer": {
+      "kind": "callable",
+      "signature": "(model: Any, tokenizer: Any, config: Any, dataset: Dict[str, Any])"
+    },
+    "SyntheticDataGenerator": {
+      "kind": "callable",
+      "signature": "(config)"
+    },
+    "TrainResult": {
+      "fields": [
+        "success",
+        "metrics",
+        "final_model_path",
+        "reverted",
+        "error",
+        "benchmark_scores",
+        "benchmark_average",
+        "benchmark_passed",
+        "resource_usage",
+        "safety_passed",
+        "safety_score",
+        "safety_categories",
+        "safety_severity",
+        "safety_low_confidence",
+        "judge_score",
+        "judge_details",
+        "estimated_cost_usd",
+        "staging_path",
+        "awaiting_approval",
+        "run_id",
+        "config_hash"
+      ],
+      "kind": "dataclass"
+    },
+    "VerifyAnnexIVResult": {
+      "kind": "callable",
+      "signature": "(*, valid: 'bool', reason: 'str' = '', missing_fields: 'List[str] | None' = None, manifest_hash_actual: 'str' = '', manifest_hash_expected: 'str' = '') -> 'None'"
+    },
+    "VerifyGgufResult": {
+      "kind": "callable",
+      "signature": "(*, valid: 'bool', reason: 'str' = '', checks: 'Dict[str, Any] | None' = None) -> 'None'"
+    },
+    "VerifyIntegrityResult": {
+      "kind": "callable",
+      "signature": "(*, valid: 'bool', reason: 'str' = '', changed: 'List[str] | None' = None, removed: 'List[str] | None' = None, added: 'List[str] | None' = None, verified_count: 'int' = 0) -> 'None'"
+    },
+    "VerifyResult": {
+      "fields": [
+        "valid",
+        "entries_count",
+        "first_invalid_index",
+        "reason"
+      ],
+      "kind": "dataclass"
+    },
+    "WebhookNotifier": {
+      "kind": "callable",
+      "signature": "(config)"
+    },
+    "audit_dataset": {
+      "kind": "callable",
+      "signature": "(source: 'str', *, output_dir: 'Optional[str]' = None, near_dup_threshold: 'int' = 3, dedup_method: 'str' = 'simhash', minhash_jaccard: 'float' = 0.85, minhash_num_perm: 'int' = 128, enable_quality_filter: 'bool' = False, enable_pii_ml: 'bool' = False, pii_ml_language: 'str' = 'en', emit_croissant: 'bool' = False, workers: 'int' = 1) -> 'AuditReport'"
+    },
+    "compute_minhash": {
+      "kind": "callable",
+      "signature": "(text: 'str', *, num_perm: 'int' = 128) -> 'Optional[Any]'"
+    },
+    "compute_simhash": {
+      "kind": "callable",
+      "signature": "(text: 'str', *, bits: 'int' = 64) -> 'int'"
+    },
+    "detect_pii": {
+      "kind": "callable",
+      "signature": "(text: 'Any') -> 'Dict[str, int]'"
+    },
+    "detect_secrets": {
+      "kind": "callable",
+      "signature": "(text: 'Any') -> 'Dict[str, int]'"
+    },
+    "get_model_and_tokenizer": {
+      "kind": "callable",
+      "signature": "(config: Any) -> Tuple[Any, Any]"
+    },
+    "load_config": {
+      "kind": "callable",
+      "signature": "(config_path: str) -> forgelm.config.ForgeConfig"
+    },
+    "manage_checkpoints": {
+      "kind": "callable",
+      "signature": "(checkpoint_dir: str, action: str = 'keep') -> None"
+    },
+    "mask_pii": {
+      "kind": "callable",
+      "signature": "(text: 'Any', replacement: 'str' = '[REDACTED]', *, return_counts: 'bool' = False) -> 'Any'"
+    },
+    "mask_secrets": {
+      "kind": "callable",
+      "signature": "(text: 'Any', replacement: 'str' = '[REDACTED-SECRET]', *, return_counts: 'bool' = False) -> 'Any'"
+    },
+    "prepare_dataset": {
+      "kind": "callable",
+      "signature": "(config: 'Any', tokenizer: 'PreTrainedTokenizer') -> 'Dict[str, Any]'"
+    },
+    "run_benchmark": {
+      "kind": "callable",
+      "signature": "(model: Any, tokenizer: Any, tasks: List[str], num_fewshot: Optional[int] = None, batch_size: str = 'auto', limit: Optional[int] = None, output_dir: Optional[str] = None, min_score: Optional[float] = None) -> forgelm.benchmark.BenchmarkResult"
+    },
+    "setup_authentication": {
+      "kind": "callable",
+      "signature": "(token: Optional[str] = None) -> None"
+    },
+    "verify_annex_iv_artifact": {
+      "kind": "callable",
+      "signature": "(path: 'str') -> 'VerifyAnnexIVResult'"
+    },
+    "verify_audit_log": {
+      "kind": "callable",
+      "signature": "(path: str, *, hmac_secret: Optional[str] = None, require_hmac: bool = False) -> forgelm.compliance.VerifyResult"
+    },
+    "verify_gguf": {
+      "kind": "callable",
+      "signature": "(path: 'str') -> 'VerifyGgufResult'"
+    },
+    "verify_integrity": {
+      "kind": "callable",
+      "signature": "(model_dir: 'str') -> 'VerifyIntegrityResult'"
+    }
+  }
+}
diff --git a/tests/test_cli_subcommands.py b/tests/test_cli_subcommands.py
index e15f1eb2..a916a63a 100644
--- a/tests/test_cli_subcommands.py
+++ b/tests/test_cli_subcommands.py
@@ -139,91 +139,16 @@ def test_audit_dispatch_quality_filter_defaults_on_when_attr_absent(self):
         assert fake.called, "_run_data_audit was never invoked; patch target may have drifted"
         assert fake.call_args.kwargs["enable_quality_filter"] is True
 
-    def test_legacy_data_audit_flag_still_works(self, tmp_path):
+    def test_legacy_data_audit_flag_removed(self, tmp_path):
+        """The legacy ``forgelm --data-audit PATH`` flag was removed in v0.8.0;
+        operators use the ``forgelm audit PATH`` subcommand instead. argparse
+        now rejects the flag at the CLI boundary (exit 2)."""
         data_path = tmp_path / "data.jsonl"
-        self._make_jsonl(data_path, [{"text": "legacy alias still routes here"}])
-        out_dir = tmp_path / "audit"
-
-        with patch(
-            "sys.argv",
-            ["forgelm", "--data-audit", str(data_path), "--output", str(out_dir)],
-        ):
+        self._make_jsonl(data_path, [{"text": "x"}])
+        with patch("sys.argv", ["forgelm", "--data-audit", str(data_path)]):
             with pytest.raises(SystemExit) as exc_info:
                 main()
-            assert exc_info.value.code == EXIT_SUCCESS
-
-        # Same on-disk product as the subcommand path.
-        assert (out_dir / "data_audit_report.json").is_file()
-
-    def test_legacy_data_audit_flag_runs_quality_filter_like_subcommand(self, tmp_path):
-        # F-P7-OPUS-01 regression: the legacy `--data-audit` alias claims
-        # "same behaviour, same output" as `forgelm audit`.  The subcommand
-        # defaults --quality-filter ON (v0.6.0+), so the legacy flag must
-        # also populate quality_summary — otherwise the two invocations emit
-        # divergent EU AI Act Art. 10 governance artifacts.
-        data_path = tmp_path / "data.jsonl"
-        self._make_jsonl(
-            data_path,
-            [{"text": "1234567890 !@#$%^&*()"}, {"text": "fine prose passes the heuristics."}],
-        )
-        out_dir = tmp_path / "audit"
-
-        with patch(
-            "sys.argv",
-            ["forgelm", "--data-audit", str(data_path), "--output", str(out_dir)],
-        ):
-            with pytest.raises(SystemExit) as exc_info:
-                main()
-            assert exc_info.value.code == EXIT_SUCCESS
-
-        with open(out_dir / "data_audit_report.json", encoding="utf-8") as fh:
-            report = json.load(fh)
-        # Quality filter is ON by default for the subcommand; the legacy
-        # alias must match (populated quality_summary, not absent/empty).
-        assert report.get("quality_summary"), "legacy alias must run the quality filter like the subcommand"
-
-    def test_legacy_data_audit_flag_emits_deprecation_warning_and_audit_event(self, tmp_path):
-        """Phase 13 (Faz 13) — `--data-audit` is a documented deprecation path.
-
-        Verifies three contract points the deprecation must satisfy:
-          1. A real ``DeprecationWarning`` fires (so `python -Wd` / CI
-             warning gates surface it).
-          2. The append-only audit log records `cli.legacy_flag_invoked`
-             with the documented payload (flag / replacement / version).
-          3. The deprecated invocation still completes successfully — the
-             warning is informational, never aborts the run.
-        """
-        data_path = tmp_path / "data.jsonl"
-        self._make_jsonl(data_path, [{"text": "deprecation contract probe"}])
-        out_dir = tmp_path / "audit"
-
-        with patch(
-            "sys.argv",
-            ["forgelm", "--data-audit", str(data_path), "--output", str(out_dir)],
-        ):
-            with pytest.warns(DeprecationWarning, match=r"--data-audit"):
-                with pytest.raises(SystemExit) as exc_info:
-                    main()
-            # Contract point #3: deprecated path must still exit 0.
-            assert exc_info.value.code == EXIT_SUCCESS
-
-        # Contract point #2: audit log carries the legacy-flag breadcrumb.
-        audit_log_path = out_dir / "audit_log.jsonl"
-        assert audit_log_path.is_file(), "legacy flag should produce an audit_log.jsonl entry"
-        events = [json.loads(line) for line in audit_log_path.read_text(encoding="utf-8").splitlines() if line.strip()]
-        legacy_events = [e for e in events if e.get("event") == "cli.legacy_flag_invoked"]
-        assert len(legacy_events) == 1, f"expected exactly one legacy-flag event, got {legacy_events}"
-        evt = legacy_events[0]
-        assert evt["flag"] == "--data-audit"
-        assert evt["replacement"] == "forgelm audit"
-        # v0.7.0 cut-release moved the removal target out one minor to
-        # v0.8.0 (preserves the one-minor warning window per
-        # docs/standards/release.md#deprecation-cadence).
-        assert evt["version"] == "v0.8.0 removal"
-
-        # Sanity: the deprecated path still produced the report — the
-        # warning is purely informational.
-        assert (out_dir / "data_audit_report.json").is_file()
+        assert exc_info.value.code == 2
 
     def test_audit_quality_filter_flag(self, tmp_path):
         # Phase 12: --quality-filter populates quality_summary.
diff --git a/tests/test_config.py b/tests/test_config.py
index 916ae36f..25b7e78a 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -546,20 +546,20 @@ def test_use_dora_warning_visible_on_logger_path(self, caplog):
 # --- M3: staging_ttl deprecation reaches the logger path (F-P1-FAB-17) ---
 
 
-class TestStagingTtlDeprecationVisibility:
-    """The deprecation must surface on the CLI logger path, not only via a
-    DeprecationWarning that CPython's default filters suppress.
+class TestStagingTtlLegacyFieldRemoved:
+    """The legacy ``evaluation.staging_ttl_days`` alias was removed in v0.8.0
+    (deprecated in v0.7.0). Only the canonical ``retention.staging_ttl_days``
+    remains; ``EvaluationConfig`` has ``extra="forbid"`` so the legacy key is
+    now a hard validation error rather than a forwarded deprecation.
     """
 
-    def test_staging_ttl_forward_emits_logger_warning(self, caplog):
-        with caplog.at_level(logging.WARNING, logger="forgelm.config"):
+    def test_legacy_evaluation_staging_ttl_days_is_rejected(self):
+        with pytest.raises(ValidationError, match="staging_ttl_days"):
             ForgeConfig(**minimal_config(evaluation={"staging_ttl_days": 14}))
-        assert any("staging_ttl_days` is deprecated" in r.message for r in caplog.records)
 
-    def test_staging_ttl_match_emits_logger_warning(self, caplog):
-        with caplog.at_level(logging.WARNING, logger="forgelm.config"):
-            ForgeConfig(**minimal_config(evaluation={"staging_ttl_days": 7}, retention={"staging_ttl_days": 7}))
-        assert any("canonical block wins" in r.message for r in caplog.records)
+    def test_canonical_retention_staging_ttl_days_still_works(self):
+        cfg = ForgeConfig(**minimal_config(retention={"staging_ttl_days": 30}))
+        assert cfg.retention.staging_ttl_days == 30
 
 
 # --- L3: merge / synthetic empty-payload validators (F-P1-FAB-32) ---
diff --git a/tests/test_gdpr_erasure.py b/tests/test_gdpr_erasure.py
index 6ad30b79..750449c5 100644
--- a/tests/test_gdpr_erasure.py
+++ b/tests/test_gdpr_erasure.py
@@ -697,53 +697,6 @@ def test_no_mode_at_all_rejected(self, tmp_path: Path) -> None:
 
 
 class TestStagingTtlDeprecation:
-    def test_legacy_only_non_default_alias_forwards_with_warning(self, tmp_path: Path) -> None:
-        """Phase 20 design §3.1: legacy field with non-default value
-        forwards to retention.staging_ttl_days + emits DeprecationWarning."""
-        from forgelm.config import load_config
-
-        config_path = tmp_path / "config.yaml"
-        config_path.write_text(
-            """
-model:
-  name_or_path: gpt2
-  backend: transformers
-lora:
-  r: 8
-training:
-  trainer_type: sft
-  output_dir: ./out
-  num_train_epochs: 1
-data:
-  dataset_name_or_path: train.jsonl
-evaluation:
-  staging_ttl_days: 14
-"""
-        )
-        with pytest.warns(DeprecationWarning, match="staging_ttl_days"):
-            cfg = load_config(str(config_path))
-        assert cfg.retention is not None
-        assert cfg.retention.staging_ttl_days == 14
-
-    def test_alias_forward_warning_names_v080_removal_not_v070(self) -> None:
-        """Deprecation cadence (F-P1-FAB-16): v0.7.0 shipped without the
-        removal, so every message must name the deferred v0.8.0 target — a
-        warning still naming v0.7.0 is a past-release version stamp."""
-        from forgelm.config import ForgeConfig
-
-        with pytest.warns(DeprecationWarning) as record:
-            ForgeConfig(
-                model={"name_or_path": "gpt2"},
-                lora={},
-                training={"trainer_type": "sft"},
-                data={"dataset_name_or_path": "train.jsonl"},
-                evaluation={"staging_ttl_days": 14},
-            )
-        messages = [str(w.message) for w in record if issubclass(w.category, DeprecationWarning)]
-        assert messages, "expected an alias-forward DeprecationWarning"
-        assert any("v0.8.0" in m for m in messages)
-        assert not any("v0.7.0" in m for m in messages)
-
     def test_both_set_with_different_values_raises_config_error(self, tmp_path: Path) -> None:
         from forgelm.config import ConfigError, load_config
 
@@ -841,88 +794,6 @@ def test_canonical_block_overrides_when_legacy_omitted_from_yaml(self, tmp_path:
             f"{[str(w.message) for w in deprecation_warnings]}"
         )
 
-    def test_deprecation_warnings_attribute_to_operator_call_site(self) -> None:
-        """Wave 2b Round-5 follow-up: the two deprecation paths
-        (:meth:`ForgeConfig._apply_legacy_alias_forward` for legacy-only,
-        :meth:`ForgeConfig._emit_legacy_match_warning` for both-set-equal)
-        must surface their ``DeprecationWarning`` at the operator's
-        ``ForgeConfig(...)`` call frame, not inside Pydantic's
-        ``@model_validator`` machinery.  ``warnings.warn(..., stacklevel=5)``
-        is tuned (empirically; the exact value depends on the Pydantic v2
-        validator wrapper depth) so
-        :attr:`warnings.WarningMessage.filename` resolves to this test
-        file rather than a path inside ``pydantic/`` — ergonomics: an
-        operator looking at the warning's source line sees their own
-        ``ForgeConfig(...)`` call, not framework internals.  This test
-        is the canonical regression for that tuning: a future Pydantic
-        upgrade that changes the wrapper depth will fail this test
-        before it leaks into operators' logs.
-        """
-        import warnings as _warnings
-
-        from forgelm.config import ForgeConfig
-
-        base_kwargs = {
-            "model": {"name_or_path": "gpt2", "backend": "transformers"},
-            "lora": {"r": 8},
-            "training": {"trainer_type": "sft", "output_dir": "./out", "num_train_epochs": 1},
-            "data": {"dataset_name_or_path": "train.jsonl"},
-        }
-
-        # --- alias-forward path (legacy only, explicit non-default) ---
-        with _warnings.catch_warnings(record=True) as caught_alias:
-            _warnings.simplefilter("always")
-            ForgeConfig(**base_kwargs, evaluation={"staging_ttl_days": 14})  # operator call site
-        alias_warnings = [w for w in caught_alias if issubclass(w.category, DeprecationWarning)]
-        assert len(alias_warnings) == 1, (
-            f"alias-forward path must emit exactly one DeprecationWarning, got {len(alias_warnings)}: "
-            f"{[str(w.message) for w in alias_warnings]}"
-        )
-        alias_w = alias_warnings[0]
-        assert "evaluation.staging_ttl_days" in str(alias_w.message)
-        assert "forwards to" in str(alias_w.message)
-        assert "pydantic" not in alias_w.filename, (
-            f"_apply_legacy_alias_forward stacklevel landed inside pydantic ({alias_w.filename!r}); "
-            "operator should see their own ForgeConfig(...) call frame."
-        )
-        assert alias_w.filename == __file__, (
-            f"_apply_legacy_alias_forward stacklevel must surface at the operator call site "
-            f"(this test file: {__file__!r}); got {alias_w.filename!r}."
-        )
-
-        # --- both-set-equal path (canonical wins, warning still emitted) ---
-        with _warnings.catch_warnings(record=True) as caught_match:
-            _warnings.simplefilter("always")
-            ForgeConfig(  # operator call site
-                **base_kwargs,
-                evaluation={"staging_ttl_days": 14},
-                retention={"staging_ttl_days": 14},
-            )
-        match_warnings = [w for w in caught_match if issubclass(w.category, DeprecationWarning)]
-        assert len(match_warnings) == 1, (
-            f"both-set-equal path must emit exactly one DeprecationWarning, got {len(match_warnings)}: "
-            f"{[str(w.message) for w in match_warnings]}"
-        )
-        match_w = match_warnings[0]
-        assert "evaluation.staging_ttl_days" in str(match_w.message)
-        assert "canonical block wins" in str(match_w.message)
-        assert "pydantic" not in match_w.filename, (
-            f"_emit_legacy_match_warning stacklevel landed inside pydantic ({match_w.filename!r}); "
-            "operator should see their own ForgeConfig(...) call frame."
-        )
-        assert match_w.filename == __file__, (
-            f"_emit_legacy_match_warning stacklevel must surface at the operator call site "
-            f"(this test file: {__file__!r}); got {match_w.filename!r}."
-        )
-
-
-# ---------------------------------------------------------------------------
-# Wave 2b final-review absorption — regressions for Round-1..5 contracts
-# that landed without dedicated test coverage.  Each test pins a specific
-# absorption-tightened branch so a future "simplification" PR cannot
-# silently revert the fix and pass CI.
-# ---------------------------------------------------------------------------
-
 
 class TestSaltTruncation:
     """F-21-T-04: a corrupted/truncated salt file must surface as OSError
@@ -1098,83 +969,6 @@ def test_check_policy_no_config_succeeds_with_zero(self, tmp_path: Path) -> None
         assert ei.value.code == 0
 
 
-class TestModelCopyPreservation:
-    """F-21-T-05 / F-21-01: Round-5-followup switched the alias-forward
-    to `retention.model_copy(update={"staging_ttl_days": legacy})` so
-    other operator-set retention horizons survive the deprecation
-    forward.  Without this test, a regression to
-    `RetentionConfig(staging_ttl_days=legacy)` would silently re-discard
-    the operator's `audit_log_retention_days` (Article 12 record-keeping
-    horizon) and the suite would stay green."""
-
-    def test_alias_forward_preserves_other_retention_fields(self, tmp_path: Path) -> None:
-        from forgelm.config import load_config
-
-        cfg = tmp_path / "config.yaml"
-        cfg.write_text(
-            """
-model:
-  name_or_path: gpt2
-  backend: transformers
-lora:
-  r: 8
-training:
-  trainer_type: sft
-  output_dir: ./out
-  num_train_epochs: 1
-data:
-  dataset_name_or_path: train.jsonl
-evaluation:
-  staging_ttl_days: 14
-retention:
-  audit_log_retention_days: 1825
-"""
-        )
-        with pytest.warns(DeprecationWarning, match="staging_ttl_days"):
-            loaded = load_config(str(cfg))
-        assert loaded.retention is not None
-        # The legacy alias forwarded into the canonical block.
-        assert loaded.retention.staging_ttl_days == 14
-        # The operator-set retention horizon was preserved (model_copy
-        # contract); a fresh ``RetentionConfig(staging_ttl_days=14)``
-        # would have reset this to the 1825 default by chance, so we
-        # set it to 3650 in the YAML to force a meaningful assertion.
-        # NOTE: actually 1825 IS the default, so use a non-default value.
-
-    def test_alias_forward_preserves_non_default_retention_field(self, tmp_path: Path) -> None:
-        """Stronger version: pin a NON-default `audit_log_retention_days`
-        so the test would fail under a `RetentionConfig(staging_ttl_days=legacy)`
-        constructor regression (which would reset it to the 1825 default)."""
-        from forgelm.config import load_config
-
-        cfg = tmp_path / "config.yaml"
-        cfg.write_text(
-            """
-model:
-  name_or_path: gpt2
-  backend: transformers
-lora:
-  r: 8
-training:
-  trainer_type: sft
-  output_dir: ./out
-  num_train_epochs: 1
-data:
-  dataset_name_or_path: train.jsonl
-evaluation:
-  staging_ttl_days: 14
-retention:
-  audit_log_retention_days: 3650
-"""
-        )
-        with pytest.warns(DeprecationWarning, match="staging_ttl_days"):
-            loaded = load_config(str(cfg))
-        assert loaded.retention is not None
-        assert loaded.retention.staging_ttl_days == 14
-        # Critical: the operator-set 3650 must NOT be reset to 1825.
-        assert loaded.retention.audit_log_retention_days == 3650
-
-
 class TestAtomicityFsyncFdPinning:
     """F-21-T-06: the existing `test_atomic_rewrite_fsyncs_before_rename`
     asserts that fsync is called before replace, but not WHICH fd was

From 8e5b04537603fb04e053eed71aef2f23cfca95f4 Mon Sep 17 00:00:00 2001
From: Cemil ILIK 
Date: Tue, 16 Jun 2026 05:47:10 +0300
Subject: [PATCH 2/3] =?UTF-8?q?docs:=20sync=20v0.7.0=20=E2=86=92=20v0.8.0?=
 =?UTF-8?q?=20in=20notebook=20comments=20+=20drop=20stale=20--data-audit?=
 =?UTF-8?q?=20comment=20(PR#65=20review)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Two PR #65 review findings:
- notebooks/*.ipynb: the 'Pinned to v0.7.0' / 'from the v0.7.0 tag' prose comments still named v0.7.0 while the actual pins/URLs were already bumped to v0.8.0 — synced the comments (11 notebooks).
- forgelm/cli/_dispatch.py: removed the stale '# --data-audit operates on a JSONL file/directory only — no config needed.' leftover comment (the flag was removed in v0.8.0; the adjacent removal note stays).

Co-Authored-By: Claude Opus 4.8 (1M context) 
---
 forgelm/cli/_dispatch.py                   | 1 -
 notebooks/data_curation.ipynb              | 2 +-
 notebooks/dpo_alignment.ipynb              | 2 +-
 notebooks/galore_memory_optimization.ipynb | 2 +-
 notebooks/grpo_reasoning.ipynb             | 2 +-
 notebooks/ingestion_playground.ipynb       | 2 +-
 notebooks/kto_binary_feedback.ipynb        | 2 +-
 notebooks/multi_dataset.ipynb              | 2 +-
 notebooks/post_training_workflow.ipynb     | 2 +-
 notebooks/quickstart_sft.ipynb             | 2 +-
 notebooks/safety_evaluation.ipynb          | 2 +-
 notebooks/synthetic_data_training.ipynb    | 2 +-
 12 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/forgelm/cli/_dispatch.py b/forgelm/cli/_dispatch.py
index e3b80891..a444cdf2 100644
--- a/forgelm/cli/_dispatch.py
+++ b/forgelm/cli/_dispatch.py
@@ -213,7 +213,6 @@ def _main_inner() -> None:
         _setup_logging(log_level, json_format=json_output)
         _dispatch_subcommand(command, args)
 
-    # --data-audit operates on a JSONL file/directory only — no config needed.
     # (The legacy ``forgelm --data-audit PATH`` flag was removed in v0.8.0;
     # use the first-class ``forgelm audit PATH`` subcommand instead.)
     _maybe_run_wizard(args)
diff --git a/notebooks/data_curation.ipynb b/notebooks/data_curation.ipynb
index 88b34e2f..b730b485 100644
--- a/notebooks/data_curation.ipynb
+++ b/notebooks/data_curation.ipynb
@@ -23,7 +23,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM with the ingestion extra (PDF/DOCX/EPUB/Markdown parsers + langdetect)\n",
-    "# Pinned to v0.7.0; bump on each release.\n",
+    "# Pinned to v0.8.0; bump on each release.\n",
     "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.8.0'\n",
     "!forgelm --version"
    ]
diff --git a/notebooks/dpo_alignment.ipynb b/notebooks/dpo_alignment.ipynb
index 9f635852..1dc54b1d 100644
--- a/notebooks/dpo_alignment.ipynb
+++ b/notebooks/dpo_alignment.ipynb
@@ -20,7 +20,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n",
diff --git a/notebooks/galore_memory_optimization.ipynb b/notebooks/galore_memory_optimization.ipynb
index c938a826..17623fed 100644
--- a/notebooks/galore_memory_optimization.ipynb
+++ b/notebooks/galore_memory_optimization.ipynb
@@ -43,7 +43,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM + GaLore\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip install -q galore-torch  # GaLore optimizer\n",
     "!pip uninstall -y wandb -q\n",
diff --git a/notebooks/grpo_reasoning.ipynb b/notebooks/grpo_reasoning.ipynb
index edf0bc63..ee9c42f7 100644
--- a/notebooks/grpo_reasoning.ipynb
+++ b/notebooks/grpo_reasoning.ipynb
@@ -20,7 +20,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n",
diff --git a/notebooks/ingestion_playground.ipynb b/notebooks/ingestion_playground.ipynb
index 502f579a..d35e305f 100644
--- a/notebooks/ingestion_playground.ipynb
+++ b/notebooks/ingestion_playground.ipynb
@@ -28,7 +28,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM with the ingestion extra (PDF/DOCX/EPUB parsers + langdetect)\n",
-    "# Pinned to v0.7.0; bump on each release.\n",
+    "# Pinned to v0.8.0; bump on each release.\n",
     "!pip install -q --no-cache-dir 'forgelm[ingestion]==0.8.0'\n",
     "!forgelm --version"
    ]
diff --git a/notebooks/kto_binary_feedback.ipynb b/notebooks/kto_binary_feedback.ipynb
index 4dbabc3f..ad1816e7 100644
--- a/notebooks/kto_binary_feedback.ipynb
+++ b/notebooks/kto_binary_feedback.ipynb
@@ -20,7 +20,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n",
diff --git a/notebooks/multi_dataset.ipynb b/notebooks/multi_dataset.ipynb
index 51e80df0..401d3b5f 100644
--- a/notebooks/multi_dataset.ipynb
+++ b/notebooks/multi_dataset.ipynb
@@ -20,7 +20,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n",
diff --git a/notebooks/post_training_workflow.ipynb b/notebooks/post_training_workflow.ipynb
index f0ce1405..05e939b1 100644
--- a/notebooks/post_training_workflow.ipynb
+++ b/notebooks/post_training_workflow.ipynb
@@ -37,7 +37,7 @@
    "source": [
     "# Install ForgeLM (skip on Colab if quickstart_sft already ran in this session)\n",
     "!pip install -q --no-cache-dir 'forgelm==0.8.0' bitsandbytes\n",
-    "# Download config_template.yaml from the v0.7.0 tag (NOT main) so the YAML\n",
+    "# Download config_template.yaml from the v0.8.0 tag (NOT main) so the YAML\n",
     "# matches the wheel above — `main` drifts as features land and would otherwise\n",
     "# reference fields the installed Pydantic schema rejects.\n",
     "!wget -q https://raw.githubusercontent.com/HodeTech/ForgeLM/v0.8.0/config_template.yaml\n",
diff --git a/notebooks/quickstart_sft.ipynb b/notebooks/quickstart_sft.ipynb
index 933d6d88..82943e46 100644
--- a/notebooks/quickstart_sft.ipynb
+++ b/notebooks/quickstart_sft.ipynb
@@ -26,7 +26,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (the [qlora] extra pulls in bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0'\n",
     "!pip uninstall -y wandb -q  # Remove conflicting wandb (not needed)\n",
     "\n",
diff --git a/notebooks/safety_evaluation.ipynb b/notebooks/safety_evaluation.ipynb
index 3b50837b..706fc432 100644
--- a/notebooks/safety_evaluation.ipynb
+++ b/notebooks/safety_evaluation.ipynb
@@ -22,7 +22,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "!forgelm --version"
diff --git a/notebooks/synthetic_data_training.ipynb b/notebooks/synthetic_data_training.ipynb
index b1c5b972..21345738 100644
--- a/notebooks/synthetic_data_training.ipynb
+++ b/notebooks/synthetic_data_training.ipynb
@@ -25,7 +25,7 @@
    "outputs": [],
    "source": [
     "# Step 1: Install ForgeLM (with bitsandbytes for 4-bit quantization)\n",
-    "# Pinned to v0.7.0; bump on each release\n",
+    "# Pinned to v0.8.0; bump on each release\n",
     "!pip install -q --no-cache-dir 'forgelm[qlora]==0.8.0' bitsandbytes\n",
     "!pip uninstall -y wandb -q\n",
     "# Colab base image ships torchao 0.10; modern peft requires >=0.16 for PeftModel.from_pretrained.\n",

From bb0f191b208a077348b12ee0ac07b25e01002573 Mon Sep 17 00:00:00 2001
From: Cemil ILIK 
Date: Tue, 16 Jun 2026 06:04:35 +0300
Subject: [PATCH 3/3] docs: CHANGELOG compare links, site exit-code 0-5,
 notebook version metadata (PR#65 review)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Three still-valid PR #65 review findings (bitsandbytes-pin finding skipped — the project convention + check_notebook_pins's own accepted-pin example install 'forgelm[qlora]==X bitsandbytes' with bitsandbytes pulled in pinned via the qlora extra):
- CHANGELOG.md: bump the [Unreleased] compare link to v0.8.0...HEAD and add the [0.8.0]: v0.7.0...v0.8.0 link.
- site exit-code claim: '0 / 1 / 2 / 3 / 4' was missing code 5 (wizard-cancelled) — corrected to '0 / 1 / 2 / 3 / 4 / 5' across features.html + all 6 translations.js locales, matching forgelm/cli/_exit_codes.py.
- notebooks/{data_curation,ingestion_playground}.ipynb: added the missing language_info.version (3.11.0) to match the other tutorial notebooks.

Co-Authored-By: Claude Opus 4.8 (1M context) 
---
 CHANGELOG.md                         |  3 ++-
 notebooks/data_curation.ipynb        |  3 ++-
 notebooks/ingestion_playground.ipynb |  3 ++-
 site/features.html                   |  2 +-
 site/js/translations.js              | 12 ++++++------
 5 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 467bf520..7a5c90b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2148,7 +2148,8 @@ Major release: ForgeLM goes from a basic SFT fine-tuning tool to a full-stack LL
 - Basic evaluation checks (max loss, baseline comparison)
 - Auto-revert on quality degradation
 
-[Unreleased]: https://github.com/HodeTech/ForgeLM/compare/v0.7.0...HEAD
+[Unreleased]: https://github.com/HodeTech/ForgeLM/compare/v0.8.0...HEAD
+[0.8.0]: https://github.com/HodeTech/ForgeLM/compare/v0.7.0...v0.8.0
 [0.7.0]: https://github.com/HodeTech/ForgeLM/compare/v0.6.0...v0.7.0
 [0.6.0]: https://github.com/HodeTech/ForgeLM/compare/v0.5.7...v0.6.0
 [0.5.7]: https://github.com/HodeTech/ForgeLM/compare/v0.5.6...v0.5.7
diff --git a/notebooks/data_curation.ipynb b/notebooks/data_curation.ipynb
index b730b485..6d10eb30 100644
--- a/notebooks/data_curation.ipynb
+++ b/notebooks/data_curation.ipynb
@@ -408,7 +408,8 @@
    "name": "python3"
   },
   "language_info": {
-   "name": "python"
+   "name": "python",
+   "version": "3.11.0"
   }
  },
  "nbformat": 4,
diff --git a/notebooks/ingestion_playground.ipynb b/notebooks/ingestion_playground.ipynb
index d35e305f..d34ab6a4 100644
--- a/notebooks/ingestion_playground.ipynb
+++ b/notebooks/ingestion_playground.ipynb
@@ -419,7 +419,8 @@
    "name": "python3"
   },
   "language_info": {
-   "name": "python"
+   "name": "python",
+   "version": "3.11.0"
   }
  },
  "nbformat": 4,
diff --git a/site/features.html b/site/features.html
index feb45b8e..ca47598c 100644
--- a/site/features.html
+++ b/site/features.html
@@ -236,7 +236,7 @@ 

Trend tracking

Exit codes

-

0 / 1 / 2 / 3 / 4 — a public contract for CI/CD.

+

0 / 1 / 2 / 3 / 4 / 5 — a public contract for CI/CD.

diff --git a/site/js/translations.js b/site/js/translations.js index 55714c58..eaae2fe3 100644 --- a/site/js/translations.js +++ b/site/js/translations.js @@ -977,7 +977,7 @@ "features.eval.trend.title": "Trend tracking", "features.eval.trend.body": "Cross-run safety history (safety_trend.jsonl per output_dir). See if a category quietly drifts before it crosses the threshold.", "features.eval.exit.title": "Exit codes", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — a public contract for CI/CD. Fail-loud is the default; no silent || true.", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — a public contract for CI/CD. Fail-loud is the default; no silent || true.", "features.data.heading": "Data ingestion & audit", "features.data.body": "Raw documents to clean JSONL, with PII / secrets / quality / leakage all surfaced before training.", "features.data.multi.title": "Multi-format ingest", @@ -1082,7 +1082,7 @@ "features.eval.trend.title": "Trend izleme", "features.eval.trend.body": "Koşular arası güvenlik geçmişi (output_dir başına safety_trend.jsonl). Bir kategori eşiği aşmadan önce sessizce kayıyor mu görün.", "features.eval.exit.title": "Exit kodları", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — CI/CD için kamuya açık kontrat. Sessiz başarısızlık yok; || true yasak.", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — CI/CD için kamuya açık kontrat. Sessiz başarısızlık yok; || true yasak.", "features.data.heading": "Veri ingest'i ve denetimi", "features.data.body": "Ham dokümandan temiz JSONL'a — eğitimden önce tüm PII / sırlar / kalite / sızıntı yüzeyde.", "features.data.multi.title": "Çoklu format ingest'i", @@ -1187,7 +1187,7 @@ "features.eval.trend.title": "Trend-Tracking", "features.eval.trend.body": "Lauf-übergreifende Sicherheitshistorie (safety_trend.jsonl pro output_dir). Sehen Sie, ob eine Kategorie stillschweigend driftet, bevor sie den Schwellwert überschreitet.", "features.eval.exit.title": "Exit-Codes", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — ein öffentlicher Vertrag für CI/CD. Fail-Loud ist Standard; kein stilles || true.", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — ein öffentlicher Vertrag für CI/CD. Fail-Loud ist Standard; kein stilles || true.", "features.data.heading": "Datenaufnahme & Audit", "features.data.body": "Vom Rohdokument zum sauberen JSONL — mit PII / Secrets / Qualität / Leaks alle vor dem Training sichtbar.", "features.data.multi.title": "Multi-Format-Ingest", @@ -1266,7 +1266,7 @@ "features.eval.trend.title": "Suivi de tendance", "features.eval.trend.body": "Historique sécurité inter-exécutions (safety_trend.jsonl par output_dir). Voyez si une catégorie dérive silencieusement avant de franchir le seuil.", "features.eval.exit.title": "Codes de sortie", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — un contrat public pour CI/CD. Fail-loud par défaut ; pas de || true silencieux.", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — un contrat public pour CI/CD. Fail-loud par défaut ; pas de || true silencieux.", "features.data.heading": "Ingestion de données & audit", "features.data.body": "Du document brut au JSONL propre, avec PII / secrets / qualité / fuites tous remontés avant l'entraînement.", "features.data.multi.title": "Ingestion multi-format", @@ -1345,7 +1345,7 @@ "features.eval.trend.title": "Seguimiento de tendencias", "features.eval.trend.body": "Historial de seguridad entre ejecuciones (safety_trend.jsonl por output_dir). Vea si una categoría deriva silenciosamente antes de cruzar el umbral.", "features.eval.exit.title": "Códigos de salida", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — un contrato público para CI/CD. Fail-loud por defecto; sin || true silencioso.", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — un contrato público para CI/CD. Fail-loud por defecto; sin || true silencioso.", "features.data.heading": "Ingestión de datos y auditoría", "features.data.body": "Del documento en bruto al JSONL limpio, con PII / secretos / calidad / fugas todos a la vista antes del entrenamiento.", "features.data.multi.title": "Ingestión multi-formato", @@ -1424,7 +1424,7 @@ "features.eval.trend.title": "趋势跟踪", "features.eval.trend.body": "跨运行的安全历史 (每个 output_dir 的 safety_trend.jsonl)。在某个类别跨过阈值之前看出它是否在悄悄漂移。", "features.eval.exit.title": "退出码", - "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 — 给 CI/CD 的公共契约。默认大声失败;不允许悄悄的 || true。", + "features.eval.exit.body": "0 / 1 / 2 / 3 / 4 / 5 — 给 CI/CD 的公共契约。默认大声失败;不允许悄悄的 || true。", "features.data.heading": "数据 ingest & 审计", "features.data.body": "从原始文档到干净 JSONL,所有 PII / secrets / 质量 / 泄漏在训练前都浮上水面。", "features.data.multi.title": "多格式 ingest",