Skip to content

chore: migrate to HodeTech org + ignore new nightly CVE - #61

Merged
cemililik merged 18 commits into
mainfrom
development
Jun 14, 2026
Merged

cemililik merged 18 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented May 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two related housekeeping changes that both target main via development:

  1. Org migration — the project moved to the HodeTech GitHub organization (github.com/HodeTech/ForgeLM). Updates every reference project-wide (271 refs / 99 files, 1:1 string replacements).
  2. Nightly CVE ignore — adds transformers PYSEC-2025-217 to the project ignore list so the daily nightly goes green again.

1. Org migration

Category Change
Repo URLs (256) github.com/cemililik/ForgeLM → github.com/HodeTech/ForgeLM (README badges, pyproject.toml [project.urls], docs, user manuals EN+TR, notebooks, site/, CHANGELOG, CONTRIBUTING, issue-template, forgelm/model_card.py, standards, roadmap)
GHCR image (12) ghcr.io/cemililik/forgelm → ghcr.io/hodetech/forgelm (lowercase namespace)
Contact email ai-team@cemililik.com → forgelm@hodetech.io (CODE_OF_CONDUCT.md)
SonarCloud organization=hodetech, projectKey=HodeTech_ForgeLM (sonar-project.properties)

git grep cemililik → 0 remaining. Guards green: anchor resolution, bilingual parity, user-manual self-contained, CLI help consistency, site-version, site-claims. ruff clean; model_card tests pass.

2. Nightly CVE ignore (transformers PYSEC-2025-217)

The new --ignores tools/pip_audit_ignores.yaml mechanism works on main (the run log shows CVE-2026-1839 correctly suppressed), but a fresh no-fix advisory dropped:

  • PYSEC-2025-217 (CVE-2025-14929) — transformers X-CLIP checkpoint-conversion deserialization RCE. CVSS AV:L/UI:R (local + user must open a malicious checkpoint), affected through 5.0.0rc0, no fix in the pinned 4.x line.
  • Same threat-model carve-out as the existing transformers/torch deserialization ignores: operators are documented as required to load only trusted checkpoints, and ForgeLM does not invoke X-CLIP checkpoint conversion.
  • One-line data change in the ignore YAML — no workflow edit, exactly the maintainability win the PR fix(supply-chain): ignore 10 no-fix CVEs that broke nightly 2026-05-21 #59 review asked for.

The ignore-file loader self-validates (tools/check_pip_audit.py --ignores); 23 tests pass.

Repo-side follow-ups (not in this diff)

  • SonarCloud project under the hodetech org with key HodeTech_ForgeLM; re-add SONAR_TOKEN secret if not inherited (repo currently lists no Actions secrets).
  • GHCR package visibility / publish workflow under the new org.
  • GitHub repo description is empty.

Closes #58

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Security

    • Added stricter audit-event catalog drift checks to CI/self-validation.
    • Hardened audit-log verification against empty/truncation/tampering.
    • Improved model-card generation by redacting secret-like values.
    • Made webhook notifications more resilient when the webhook URL config is missing.
    • Added a packaging audit ignore for a transformers advisory.
  • Bug Fixes

    • Human-approval gating now uses an explicit awaiting-approval state (including correct JSON output) and blocks unsafe resume from gated stages.
    • Revert/failure outcomes now report and clear staging state consistently; webhook delivery errors no longer fail approve/reject.
  • Documentation

    • Updated repository/site/notebook links and clarified Pro feature timelines and related UX notes.
  • Chores

    • Updated metadata and container/registry references to HodeTech; made the merging extra a no-op (native merging).

cemililik and others added 2 commits May 25, 2026 01:07
The project moved to the HodeTech GitHub organization
(github.com/HodeTech/ForgeLM). Update every reference project-wide:

- Repo URLs: github.com/cemililik/ForgeLM -> github.com/HodeTech/ForgeLM
  (README badges, pyproject [project.urls], docs, user manuals EN+TR,
  notebooks, site/, CHANGELOG, CONTRIBUTING, issue-template config,
  model_card.py, standards, roadmap).
- GHCR image: ghcr.io/cemililik/forgelm -> ghcr.io/hodetech/forgelm
  (lowercase namespace, per GHCR owner naming).
- Contact email (CODE_OF_CONDUCT.md): ai-team@cemililik.com ->
  forgelm@hodetech.io.
- SonarCloud (sonar-project.properties): organization=hodetech,
  projectKey=HodeTech_ForgeLM.

271 references across 99 files, 1:1 replacements (no other edits).
Local git remote origin re-pointed to the HodeTech URL.

Doc/site guards re-run green: anchor resolution, bilingual parity,
user-manual self-contained, CLI help consistency, site-version,
site-claims. model_card tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5-24)

The nightly on main is green through the new --ignores mechanism (the
run log shows CVE-2026-1839 correctly suppressed), but a new no-fix
advisory dropped: transformers PYSEC-2025-217 (CVE-2025-14929),
X-CLIP checkpoint-conversion deserialization RCE.

CVSS is AV:L/UI:R (local + user must open a malicious checkpoint),
affected through 5.0.0rc0 with no fix in the pinned 4.x line. Same
threat-model carve-out as the existing transformers/torch
deserialization ignores: operators are documented as required to load
only trusted checkpoints, and ForgeLM does not invoke X-CLIP
checkpoint conversion.

With the Phase-refactored ignore file this is a one-line data change,
no workflow edit — exactly the maintainability win the PR #59 review
asked for.

Refs: #58

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cemililik cemililik changed the title chore: migrate repo references to HodeTech org chore: migrate to HodeTech org + ignore new nightly CVE May 24, 2026
@cemililik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review all

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

@cemililik Sure! I'll kick off a full review of all the changes in this PR right away.

✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cemililik, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 1 hour, 5 minutes, and 41 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d1910ff-beed-46e7-b0fc-db378ff11b87

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc4f58 and 7a63783.

📒 Files selected for processing (2)
  • forgelm/trainer.py
  • tests/test_trainer_judge_env.py
📝 Walkthrough

Walkthrough

This PR updates repository and registry references across documentation and site assets, adds audit-event catalog drift checks and pipeline lifecycle catalog entries, and changes training, model-loading, webhook, and audit-log behavior with matching tests. It also adds a pip-audit ignore entry and refreshes related contract documentation.

Changes

Repository and documentation migration

Layer / File(s) Summary
Metadata and entrypoint links
.agents/..., .claude/..., .github/..., pyproject.toml, sonar-project.properties, CODE_OF_CONDUCT.md
Updated skill templates, issue/contact links, contributor guidance, project URLs, Sonar identifiers, and the reporting contact email to current repository and organization references.
Guides, standards, and reference links
README.md, CONTRIBUTING.md, CHANGELOG.md, docs/guides/*, docs/reference/*, docs/roadmap/*, docs/standards/*, docs/usermanuals/**/compliance/*, docs/usermanuals/**/data/*, docs/usermanuals/**/deployment/*, docs/usermanuals/**/evaluation/*, docs/usermanuals/**/getting-started/*, docs/usermanuals/**/operations/*
Repointed repository, issue, release, roadmap, compliance, deployment, and reference links to HodeTech/ForgeLM and updated the merging and exit-code wording in usage and standards docs.
Site, notebook, and translation links
site/*.html, site/js/*.js, notebooks/*.ipynb, forgelm/model_card.py
Updated site metadata, footer links, notebook Colab and fallback URLs, site edit-base configuration, and model-card attribution links to the new repository paths.

Pipeline, audit, and training behavior

Layer / File(s) Summary
Audit catalog guard and event vocabulary
tools/check_audit_event_catalog.py, docs/reference/audit_event_catalog*.md, tests/test_check_audit_event_catalog.py, .github/workflows/ci.yml, AGENTS.md, CLAUDE.md
Restricted audit-event detection to explicit emission contexts, added pipeline lifecycle event rows, wired the strict CI guard, and added tests for catalog drift and detection edge cases.
Pipeline approval and resume flow
forgelm/cli/_pipeline.py, forgelm/cli/_result.py, forgelm/cli/_training.py, forgelm/results.py, docs/usermanuals/*/reference/json-output.md, tests/test_pipeline_orchestrator.py, tests/test_cost_estimation.py, tests/test_human_approval_gate.py
Added awaiting_approval to the result envelope and training result state, changed stage handling to treat failed results as failures before approval checks, and refused resume-from past gated pending approval stages.
Compliance verification and webhook persistence
forgelm/compliance.py, forgelm/webhook.py, tests/test_compliance.py, tests/test_webhook.py, forgelm/cli/subcommands/_approve.py
Derived persisted webhook fields from the live schema, hardened empty-log verification, made webhook URL resolution tolerate missing url attributes, and made approve/reject webhook notifications best-effort.
Model loading, trainer configuration, and merging behavior
forgelm/trainer.py, forgelm/model.py, forgelm/merging.py, forgelm/config.py, forgelm/model_card.py, tests/test_trainer_*, tests/test_model_*, tests/test_merging_algos.py, tests/test_oom_recovery.py, tests/test_packaging.py
Propagated model.max_length into trainer configs, validated judge API key environment handling, changed MoE and multimodal loading behavior, adjusted merging thresholds and optional-dependency metadata, added secret redaction for model cards, and exported OOM recovery details in compliance manifests.

Security policy and vulnerability ignore

Layer / File(s) Summary
Transformers advisory ignore
tools/pip_audit_ignores.yaml
Added a pip-audit ignore entry for PYSEC-2025-217 / CVE-2025-14929 with the documented threat model, verification timestamp, and reevaluation policy.
  • Estimated code review effort: 🎯 5 (Critical) | ⏱️ ~120 minutes

  • Possibly related PRs:

    • HodeTech/ForgeLM#54: overlaps with the Phase 14 pipeline, awaiting_approval state, and stage-gate orchestration changes.
    • HodeTech/ForgeLM#39: overlaps with tools/check_audit_event_catalog.py and the CI guard around audit-event catalog drift.
    • HodeTech/ForgeLM#60: overlaps with the tools/pip_audit_ignores.yaml policy entry and related vulnerability suppression flow.

"🐇 Hoppity hop, the links are new,
The gates now know what they should do.
Audit songs and trainer trails,
Go steady through the nightly gales.
HodeTech moon, ForgeLM bloom!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
docs/usermanuals/en/deployment/chat.md (1)

110-110: ⚡ Quick win

Misleading equivalence between chat-compare and benchmark-only.

The note states "Today the same comparison runs through forgelm --benchmark-only", but the --benchmark-only mode runs lm-evaluation-harness benchmark tasks (forgelm/cli/_no_train_modes.py:1-140), which is fundamentally different from the side-by-side chat prompt comparison shown in lines 113-119. Consider revising to acknowledge that no direct equivalent exists today, or clarify that operators must manually run benchmarks separately on each model and compare the judge results themselves (which is a different workflow from the planned dedicated UX).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/usermanuals/en/deployment/chat.md` at line 110, The note incorrectly
implies feature parity between the planned chat-compare subcommand and the
existing --benchmark-only mode; update the text to state that chat-compare
provides an interactive side-by-side chat prompt comparison while
--benchmark-only (see forgelm/cli/_no_train_modes.py) runs lm-evaluation-harness
benchmarks and requires manually running benchmarks per checkpoint and
aggregating judge results, so operators must currently run and compare results
themselves rather than getting the planned dedicated UX.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/usermanuals/en/deployment/chat.md`:
- Around line 66-68: Resolve two issues: pick one consistent flag form (e.g.,
use "--safety" or "--safety on" everywhere) and update the paragraph to use that
chosen form; and clarify the current v0.5.5 behavior by either (A) describing
the exact binding workflow (pointing readers to the chat subcommand parser in
forgelm/cli/_parser.py and specifying which config file/key to set and how the
runner must load it) or (B) explicitly state that on v0.5.5 the CLI does not
support per-turn safety screening and that the YAML safety: enabled: true entry
is only read by certain runners (name them) — do not claim a binding mechanism
exists unless you spell out the exact steps referencing forgelm/cli/_parser.py.

In `@site/js/translations.js`:
- Line 4345: The privacy copy currently hardcodes "pip install forgelm==0.5.5"
in the "privacy.body" locale string (and the same hardcoded version appears in
the sibling privacy locale entries noted in the comment); change this to avoid a
stale pinned version by either removing the "==0.5.5" (use "pip install
forgelm") or replace it with a template/variable (e.g., "{FORGELM_PIP}" or
"{siteVersion}") that the site build injects so the install line is maintained
site-wide and updated from a single source; locate and update the string key
"privacy.body" (and the other privacy locale keys mentioned) to use the
non-hardcoded form.

In `@tools/pip_audit_ignores.yaml`:
- Around line 51-69: Update the PYSEC-2025-217 ignore entry (id: PYSEC-2025-217,
package: transformers) to match advisory wording: state CVE-2025-14929 with CVSS
vector including AV:L and UI:R, remove the unverified phrase "affected through
5.0.0rc0" and instead say "no fixed version announced for the 4.x line" (or
mirror the advisory language about lack of a fix), and adjust the
reevaluate_after criteria accordingly; additionally perform and document a
codebase check to confirm the threat-model claim by searching for usages of
X-CLIP checkpoint conversion paths (search for identifiers/strings such as
"X-CLIP", "checkpoint conversion", "pt2 loader", and any direct "torch.load"
checkpoint-loading sites) and record the result in the entry.

---

Nitpick comments:
In `@docs/usermanuals/en/deployment/chat.md`:
- Line 110: The note incorrectly implies feature parity between the planned
chat-compare subcommand and the existing --benchmark-only mode; update the text
to state that chat-compare provides an interactive side-by-side chat prompt
comparison while --benchmark-only (see forgelm/cli/_no_train_modes.py) runs
lm-evaluation-harness benchmarks and requires manually running benchmarks per
checkpoint and aggregating judge results, so operators must currently run and
compare results themselves rather than getting the planned dedicated UX.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3115152d-2066-4049-a433-0504b4e6c23f

📥 Commits

Reviewing files that changed from the base of the PR and between a2bdb82 and 0613f80.

📒 Files selected for processing (100)
  • .agents/skills/cut-release/SKILL.md
  • .agents/skills/sync-bilingual-docs/SKILL.md
  • .claude/skills/cut-release/SKILL.md
  • .claude/skills/sync-bilingual-docs/SKILL.md
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/workflows/nightly.yml
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • README.md
  • docs/guides/quickstart-tr.md
  • docs/guides/quickstart.md
  • docs/guides/troubleshooting-tr.md
  • docs/guides/troubleshooting.md
  • docs/reference/usage-tr.md
  • docs/reference/usage.md
  • docs/roadmap/releases.md
  • docs/roadmap/risks-and-decisions.md
  • docs/standards/code-review.md
  • docs/standards/documentation.md
  • docs/usermanuals/en/compliance/annex-iv.md
  • docs/usermanuals/en/compliance/audit-log.md
  • docs/usermanuals/en/compliance/gdpr-erasure.md
  • docs/usermanuals/en/compliance/gdpr.md
  • docs/usermanuals/en/compliance/human-approval-gate.md
  • docs/usermanuals/en/compliance/overview.md
  • docs/usermanuals/en/compliance/verify-audit.md
  • docs/usermanuals/en/data/deduplication.md
  • docs/usermanuals/en/data/pii-masking.md
  • docs/usermanuals/en/deployment/chat.md
  • docs/usermanuals/en/deployment/model-merging.md
  • docs/usermanuals/en/deployment/verify-gguf.md
  • docs/usermanuals/en/evaluation/trend-tracking.md
  • docs/usermanuals/en/getting-started/installation.md
  • docs/usermanuals/en/operations/docker.md
  • docs/usermanuals/en/operations/experiment-tracking.md
  • docs/usermanuals/en/operations/gpu-cost.md
  • docs/usermanuals/en/operations/iso-soc2-deployer.md
  • docs/usermanuals/en/operations/supply-chain.md
  • docs/usermanuals/en/operations/troubleshooting.md
  • docs/usermanuals/en/operations/webhooks.md
  • docs/usermanuals/en/reference/configuration.md
  • docs/usermanuals/en/reference/exit-codes.md
  • docs/usermanuals/en/reference/json-output.md
  • docs/usermanuals/en/reference/library-api.md
  • docs/usermanuals/en/reference/performance.md
  • docs/usermanuals/en/reference/yaml-templates.md
  • docs/usermanuals/tr/compliance/annex-iv.md
  • docs/usermanuals/tr/compliance/audit-log.md
  • docs/usermanuals/tr/compliance/gdpr-erasure.md
  • docs/usermanuals/tr/compliance/gdpr.md
  • docs/usermanuals/tr/compliance/human-approval-gate.md
  • docs/usermanuals/tr/compliance/overview.md
  • docs/usermanuals/tr/compliance/verify-audit.md
  • docs/usermanuals/tr/data/deduplication.md
  • docs/usermanuals/tr/data/pii-masking.md
  • docs/usermanuals/tr/deployment/chat.md
  • docs/usermanuals/tr/deployment/model-merging.md
  • docs/usermanuals/tr/deployment/verify-gguf.md
  • docs/usermanuals/tr/evaluation/trend-tracking.md
  • docs/usermanuals/tr/getting-started/installation.md
  • docs/usermanuals/tr/operations/docker.md
  • docs/usermanuals/tr/operations/experiment-tracking.md
  • docs/usermanuals/tr/operations/gpu-cost.md
  • docs/usermanuals/tr/operations/iso-soc2-deployer.md
  • docs/usermanuals/tr/operations/supply-chain.md
  • docs/usermanuals/tr/operations/troubleshooting.md
  • docs/usermanuals/tr/operations/webhooks.md
  • docs/usermanuals/tr/reference/configuration.md
  • docs/usermanuals/tr/reference/exit-codes.md
  • docs/usermanuals/tr/reference/json-output.md
  • docs/usermanuals/tr/reference/library-api.md
  • docs/usermanuals/tr/reference/performance.md
  • docs/usermanuals/tr/reference/yaml-templates.md
  • forgelm/model_card.py
  • notebooks/data_curation.ipynb
  • notebooks/dpo_alignment.ipynb
  • notebooks/galore_memory_optimization.ipynb
  • notebooks/grpo_reasoning.ipynb
  • notebooks/ingestion_playground.ipynb
  • notebooks/kto_binary_feedback.ipynb
  • notebooks/multi_dataset.ipynb
  • notebooks/post_training_workflow.ipynb
  • notebooks/quickstart_sft.ipynb
  • notebooks/safety_evaluation.ipynb
  • notebooks/synthetic_data_training.ipynb
  • pyproject.toml
  • site/README.md
  • site/compliance.html
  • site/contact.html
  • site/features.html
  • site/guide.html
  • site/index.html
  • site/js/guide.js
  • site/js/translations.js
  • site/privacy.html
  • site/quickstart.html
  • site/terms.html
  • sonar-project.properties
  • tools/pip_audit_ignores.yaml

Comment thread docs/usermanuals/en/deployment/chat.md Resolved
Comment thread site/js/translations.js Resolved
Comment thread tools/pip_audit_ignores.yaml Resolved
cemililik and others added 14 commits May 25, 2026 17:50
…acy pin, PYSEC-2025-217)

All four findings verified against current code; all still valid.

chat.md (EN+TR) — safety routing (L66-68):
  Verified forgelm/cli/_parser.py: `forgelm chat` has no --safety flag
  and loads no config; run_chat/ChatSession take no safety arg. The old
  copy claimed a YAML `safety:` binding into the chat session — false,
  and the key path was wrong. Rewritten to state the current release
  does NOT screen per-turn in the REPL, and that
  `evaluation.safety.enabled: true` is consumed only by the training
  auto-revert gate (forgelm/trainer.py) and `forgelm safety-eval`.
  Collapsed the inconsistent `--safety` / `--safety on` forms to a
  single `--safety`.

chat.md (EN+TR) — chat-compare note (L110):
  Verified forgelm/cli/_no_train_modes.py: --benchmark-only runs
  lm-evaluation-harness on a SINGLE checkpoint, no side-by-side, no
  judge. Removed the implied parity; now says operators must run
  --benchmark-only per checkpoint, run the judge separately, and
  aggregate themselves until the planned chat-compare UX ships.

site/js/translations.js — privacy.body (6 locales):
  Dropped the stale hard-coded `pip install forgelm==0.5.5` pin (real
  release is 0.7.0; this string is outside update_site_version.py's
  scope so it drifted). Now `pip install forgelm` — no version to
  drift, consistent with the site's no-build-step constraint.

tools/pip_audit_ignores.yaml — PYSEC-2025-217:
  Reworded to advisory language: full CVSS vector
  (AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H), "no fixed version for the 4.x
  line" instead of "affected through 5.0.0rc0", reevaluate_after no
  longer assumes a fixed 5.x exists. Documented the codebase check
  (git grep over forgelm/): no X-CLIP usage, no checkpoint-conversion
  path besides HF->GGUF export, zero direct torch.load sites — all
  loading via from_pretrained, so the vulnerable path is unreachable.

Validated: bilingual parity, anchor resolution, user-manual
self-contained, site-version, site-claims, site chrome parity, CLI
help consistency, ignore-file schema; 23 check_pip_audit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on validation split (C1/XP-01)

GRPO trains on generation-based rewards, not validation loss, so
`_build_grpo_trainer` pops the `eval_dataset`. But the GRPO branch of
`_get_training_args_for_type` left `eval_strategy="steps"` in place — the
value `_get_common_training_kwargs` sets whenever a validation split exists,
which is the *default* pipeline path (auto-created split). A GRPOConfig with
`eval_strategy="steps"` and no eval_dataset makes HF/TRL raise
`ValueError: ... you didn't pass an eval_dataset` at GRPOTrainer construction.

Force `eval_strategy="no"` and clear `eval_steps` in lockstep with the popped
eval_dataset (the best-model args were already popped).

Regression test `test_grpo_config_disables_eval_when_validation_split_present`
asserts ForgeLM's arg-reconciliation invariant (not TRL's raise): with a
validation split present, the GRPOConfig kwargs carry `eval_strategy="no"`,
no `eval_steps`, and none of the eval-coupled best-model args. Guarded on
`GRPOConfig` importability (the actual precondition) rather than `GRPOTrainer`.

Findings: F-P2-FAB-05, F-P3-FABLE-01 (XP-01).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ebhook persist-field drift (C4/XP-03)

`_WEBHOOK_PERSIST_FIELDS` had drifted from the real `WebhookConfig` schema: it
persisted six fields that don't exist (`notify_on_revert`,
`notify_on_awaiting_approval`, `secret_env`, `timeout_seconds`, `retry_count`,
`retry_backoff_seconds`) and dropped the real `url`/`notify_on_start`/`timeout`.
When `forgelm approve`/`reject` rebuilt a WebhookNotifier from the co-located
compliance manifest via a SimpleNamespace, `_resolve_url` read
`self.config.url` and raised AttributeError — *after* the model was already
promoted and the `human_approval.granted` audit event committed, crashing a
successful enterprise run and violating the public exit-code contract.

Three-part fix:
1. compliance.py — derive `_WEBHOOK_PERSIST_FIELDS` from `WebhookConfig.model_fields`
   (single source of truth, secret-bearing `url` excluded) so the persisted
   shape can never drift from the model again.
2. webhook.py — `_resolve_url` reads `getattr(self.config, "url", None)` so a
   rebuilt namespace missing `url` resolves cleanly instead of crashing.
3. _approve.py — wrap the post-promotion notifier emit (approve + reject) so a
   webhook hiccup is non-fatal (a notification is not a gate).

Regression tests (TestWebhookPersistRoundTrip): field-set parity vs the live
schema; `_resolve_url` tolerates a namespace with no `url`; inline `url` secret
never reaches the manifest while `url_env` survives; and an end-to-end rebuild
via the real `_build_approval_notifier` fires `notify_success` without crashing.

Findings: F-P1-FAB-02, F-P4-OPUS-09 (XP-03).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d awaiting_approval discriminator (C2/XP-02)

A reverted approval-gated stage was misreported as "awaiting human approval"
(exit 4) instead of "eval-failure" (exit 3): when require_human_approval was on,
the pipeline eagerly set staging_path before the post-train gates ran, but the
benchmark/safety/judge revert branches set success=False without clearing it —
and _run_single_stage checked `if result.staging_path` (→ exit 4) BEFORE
`if not result.success` (→ exit 3). The revert had already deleted the staging
dir, so the run was reported as awaiting approval against a non-existent
artefact. TrainResult / the JSON envelope had no field distinguishing the two.

Fix:
- results.py: add `awaiting_approval: bool` — the authoritative discriminator
  (set True only when the gate genuinely fires alongside success=True).
- trainer.py: the gate sets `awaiting_approval=True`; the three gate revert
  branches clear `staging_path` (revert deletes the dir).
- _pipeline.py: route on `success` first (exit 3), then on the explicit
  `awaiting_approval` flag (exit 4) — not `bool(staging_path)`, which can
  survive a revert.
- _training.py: single-run path routes on the result discriminator, not the
  bare config flag.
- _result.py: JSON envelope carries `awaiting_approval` (always) + `staging_path`
  (when staged).
- json-output.md (EN+TR): correct the false `success: true ⟺ exit 0` claim and
  document the full training result envelope (success/awaiting/reverted).

Tests: pipeline reverted-gated → exit 3; genuine gate → exit 4 via discriminator;
stale-staging-on-failure defends to exit 3; the gate sets the flag and revert
clears staging_path; envelope discriminates all three outcomes. Fixed the
masking orchestrator test that encoded the success=False-as-gated bug.

Findings: F-P1-FAB-12, F-P1-FAB-13, F-P1-FAB-30, F-P2-FAB-02, F-P2-FAB-14 (XP-02).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(C3/P2-FAB-01)

The resume skip-list decided "already done" from disk presence:
``status in {completed, gated_pending_approval} and os.path.isdir(output_model)``.
A ``gated_pending_approval`` stage's ``output_model`` is its UNAPPROVED staging
directory (``final_model.staging.<run_id>``), which exists on disk — so
``--resume-from <later_stage>`` silently skipped the gated stage and chained
downstream training from a model no human had signed off, defeating the
Article 14 human-approval gate.

Decide skip-eligibility on the state-file status, not disk presence: only a
``completed`` stage (with its output_model on disk) is skipped. A
``gated_pending_approval`` stage in the resume range is now refused with
EXIT_CONFIG_ERROR and an actionable message telling the operator to approve or
reject first — never silently bypassed. (The approval workflow that flips an
approved stage to a skippable status is handled in the Wave-1 resume/terminal
package; until then resuming past an un-approved gate is correctly blocked.)

Regression test: a gated first stage + ``--resume-from dpo_stage`` without
approval is refused (EXIT_CONFIG_ERROR) and instantiates zero downstream
trainers. Replaces the prior test that asserted the insecure skip.

Findings: F-P2-FAB-01.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…enesis manifest (C6/P4-OPUS-01)

`verify_audit_log` early-returned `VerifyResult(valid=True, entries_count=0)`
for an empty log BEFORE the genesis-manifest cross-check ran — so an attacker
who truncated the audit log to zero entries passed verification clean, even
though the genesis manifest sidecar pins a real non-empty first entry. This is
the exact truncation attack the manifest exists to detect; the in-chain
hash-continuity walk can't catch it because there are no lines left to walk.

- `_verify_genesis_manifest`: when the manifest pins a first entry but
  entries_count == 0, return valid=False with a truncate-to-empty reason.
- `verify_audit_log`: consult the genesis manifest before reporting a clean
  empty chain; a manifest-less empty log stays legitimately valid.

Tests: truncate-to-empty with manifest → valid=False; empty log without a
manifest → valid=True (benign first-run); and a re-root tamper (wipe body, keep
write-once manifest, write a fresh valid chain) → manifest-mismatch valid=False
(P4-OPUS-22 genesis-mismatch coverage that was previously untested).

Findings: F-P4-OPUS-01 (+ F-P4-OPUS-22 test coverage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ test the audit-catalog guard (C7/XP-04)

`tools/check_audit_event_catalog.py` exited 1 at HEAD, was wired into no
workflow, and had no own test — so six genuine `pipeline.*` stage/decision
events (`started`, `force_resume`, `stage_started`, `stage_completed`,
`stage_gated`, `stage_reverted`) drifted into the code uncatalogued with zero
CI tripwire on a public EU AI Act Art. 12 contract (append-only audit log).

- docs/reference/audit_event_catalog.md (+ -tr mirror): add the 6 pipeline.*
  rows with accurate when-emitted / payload / Article columns.
- check_audit_event_catalog.py: tighten the emission detector to match only
  real emission contexts (log_event/_audit_event first-arg, event= kwarg,
  "event": JSON value, _EVT_* constant declarations) instead of any quoted
  dotted string — kills the `training.output_dir` config-path false positive
  (F-P8-C-12) while dropping zero genuine events (verified empirically).
- ci.yml + CLAUDE.md/AGENTS.md gauntlet: wire the guard with --strict so the
  drift can never recur silently.
- tests/test_check_audit_event_catalog.py: happy path, undocumented-event and
  ghost-row failures, the config-path-not-flagged regression, constant/keyword
  emission detection, real-repo-in-sync, and meta-assertions that the guard is
  wired into CI and the gauntlet.

Findings: F-P8-C-01 (XP-04: F-P4-OPUS-19 + F-P8-OPUS-03/06), F-P8-C-12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…enerated README (C5/P1-FAB-01)

`SyntheticConfig.model_dump` overrides redaction of `api_key`, but Pydantic v2's
parent `ForgeConfig.model_dump()` recurses via its OWN serializer and never
calls the child's Python-level override — so the redaction was dead on the
parent-serialization path. The model-card exclude set
(`{auth, webhook, monitoring}`) also omitted `synthetic`, so a populated
`synthetic.api_key` landed in the generated `README.md` in plaintext.

Two-layer defence in model_card.py:
1. Add `synthetic` to the section `exclude` (matches auth/webhook/monitoring).
2. `_redact_secrets`: recursively mask any secret-keyed value (`api_key`,
   `token`, `secret`, `password`, ...) in the serialized config, while keeping
   `*_env` env-var-NAME references intact. Catches residual secrets in any
   non-excluded section, forward-compatibly.

Chose the model-card-side fix over a config field_serializer to avoid the
`merge_pipeline_stage_config` round-trip hazard (a redaction literal becoming
the live value — F-P1-FAB-03 / H6 territory).

Tests: `synthetic.api_key` never appears in the rendered card; the recursive
masker redacts `judge_api_key`/`password` but keeps `*_env` names.

Findings: F-P1-FAB-01.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…set judge API key env (H1a / F-P3-FABLE-03,18)

F-P3-FABLE-03: only the SFT branch passed model.max_length to TRL; DPO/ORPO/
SimPO/KTO silently fell back to TRL's 512/1024 max_length defaults and GRPO to
its 512 max_prompt_length default, while the config field, configuration.md,
and the Article 11 compliance manifest all claimed model.max_length applied —
silent truncation plus a false statement in an audit artefact. Added
`_apply_max_length` and wired it into the four preference branches (max_length)
and GRPO (max_prompt_length). A future TRL rename now surfaces as a loud
TypeError, never silent truncation.

F-P3-FABLE-18: a configured `judge_api_key_env` that is unset silently flipped
the judge to local mode (judge.py treats api_key=None as "local"), running a
different evaluator than configured; with auto_revert=true a failed local load
deletes the trained adapters over a misdiagnosed env-var problem. The trainer
now raises ConfigError naming the variable BEFORE invoking the judge, so the
model is preserved (auto-revert never fires) and the error is actionable.

Tests: per-trainer max_length application (DPO/GRPO verified; ORPO/KTO/SimPO
skip where trl can't import the config in the local env); judge env unset →
ConfigError + run_judge_evaluation never called; env set → API mode runs.

Findings: F-P3-FABLE-03, F-P3-FABLE-18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…method, loud non-CUDA 4-bit, VLM pad guard (H1b / F-P3-FABLE-07,09,10,11)

F-P3-FABLE-07: `_apply_moe_config` gated MoE behaviour on
`hasattr(config, "num_local_experts")`, which only Mixtral/Phi expose — Qwen3-MoE
(`num_experts`) and DeepSeek-V3 (`n_routed_experts`) silently no-op'd despite the
README advertising them. Resolve the expert count via an attribute registry and
WARN loudly when moe is configured but no expert attribute is found.

F-P3-FABLE-09: the unsloth backend passed only the deprecated boolean shortcuts,
silently downgrading `method: dora`/`rslora` to plain LoRA and dropping
`method: pissa` — identical YAML trained a different algorithm than the
transformers backend. Both backends now derive flags from the shared
`_resolve_peft_flags`; pissa+unsloth raises ConfigError instead of silently
degrading.

F-P3-FABLE-10: `load_in_4bit=true` on a non-CUDA host loaded full precision with
zero log output while compliance artefacts still claimed "4-bit NF4". Added a
loud WARNING that the run is not quantized and artefacts will overstate it.

F-P3-FABLE-11: the multimodal path read `tokenizer.pad_token_id` unguarded, but
an AutoProcessor exposes pad_token only on its inner `.tokenizer` (AttributeError
crash), and loaded VLMs via AutoModelForCausalLM (most are registered under
AutoModelForImageTextToText). Resolve the inner tokenizer for pad handling and
select the image-text-to-text auto-class when multimodal is enabled.

Findings: F-P3-FABLE-07, F-P3-FABLE-09, F-P3-FABLE-10, F-P3-FABLE-11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…S merge works on real models (H1c / F-P3-FABLE-19)

`_ties_merge_tensor` computed the trim threshold with `torch.quantile`, which
hard-fails above 2^24 (16,777,216) elements (PyTorch #64947). Every real-model
weight tensor exceeds this — a 7B mlp.gate_proj is ~45M elements — so the
DEFAULT MergeConfig method (`ties`) crashed on any non-toy model with the
cryptic "quantile() input tensor is too large", swallowed into MergeResult and
surfaced as a CLI exit 2 that gave the operator no clue the feature was broken.

`kthvalue` has no size limit and yields the same trim threshold: the k-th
smallest magnitude, k = trim_fraction · n. DARE was unaffected (no quantile).

Regression test: `_ties_merge_tensor` on a tensor just past 2^24 elements
returns the same shape without raising and trims ~20% to zero.

Findings: F-P3-FABLE-19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… exception-safe restore (H1d / F-P3-FABLE-04)

OOM recovery mutates config.training.per_device_train_batch_size in place. The
compliance manifest restored the configured (pre-OOM) value while the model card
read the effective (post-OOM) value, so the two Article-11 artefacts silently
contradicted (manifest=16, README=4) with nothing explaining the discrepancy.
The save/restore was also not exception-safe: a manifest-build error skipped the
restore, leaving config holding the configured values under the outer BLE001 catch.

- Record BOTH values + an explicit `oom_recovery` block (applied, configured_*,
  effective_*) in the manifest when OOM occurred, so an auditor sees the
  discrepancy explained rather than two artefacts that just disagree.
- Wrap the restore in try/finally so the effective values are always restored.

The manifest's primary batch_size keeps the configured value (existing contract
+ test unchanged); the new block is additive.

Tests: oom_recovery block present with both values after a simulated OOM; absent
when no OOM; config restored to effective values when manifest generation raises.

Findings: F-P3-FABLE-04.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he native merge (H1e / F-P3-FABLE-20)

mergekit was declared in the `merging` extra, advertised in README/usage,
probed by `forgelm doctor`, and named in config field descriptions + the
architecture standard — but never imported anywhere. Model merging is fully
native (peft + torch on state dicts), as docs/reference/architecture.md already
stated ("no mergekit dependency required"). The unused extra installed a heavy
package with its own tight transformers/torch pins that could downgrade a
training env for zero benefit, and `forgelm doctor` warned about a "missing
merge backend" on systems where merge works perfectly.

Aligned all surfaces to the native implementation: removed mergekit from the
`merging` extra (kept empty for `pip install forgelm[merging]` backward-compat),
dropped the doctor probe row, reworded config.py descriptions, README, usage.md
(+ TR), the architecture standard's heavy-deps list + extras matrix, and the
merging.py BLE001 rationale.

Regression tests: no extra declares mergekit; the `merging` extra is an empty
no-op.

Findings: F-P3-FABLE-20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…model (H1f / F-P3-FABLE-02,08)

Both MoE expert operations ran BEFORE get_peft_model, so neither did anything
the operator asked for:

F-P3-FABLE-02: `_freeze_unselected_experts` set requires_grad=False on base
expert weights one line before get_peft_model, which then re-freezes every base
parameter and makes only the injected `lora_*` adapters trainable — so the
post-PEFT trainable set was identical whether experts_to_train was set or not,
while the log asserted a selective-training outcome that never materialised.

F-P3-FABLE-08: `_apply_moe_expert_quantization` ran before any freezing, so its
"recast frozen expert weights" sweep always found nothing (permanent no-op on
the non-4bit path); on the default 4-bit path it recast packed Params4bit uint8
storage to bf16, corrupting the quant_state (crash or garbage at first forward).

Restructured the flow: `_detect_moe_experts` resolves the count pre-PEFT;
`_apply_moe_post_peft` runs selection + quantization AFTER get_peft_model.
`_freeze_unselected_experts` now freezes the LoRA adapters of unselected experts
(and warns when target_modules cover no experts, so selection has no effect).
Quantization skips entirely when the model is 4-bit-loaded and never casts
packed uint8/int8 storage; per-tensor recast failures now surface at WARNING.

Tests: selection now constrains the adapter set (differs from "all"); no-expert-
adapter case warns; 4-bit model skips quantization (uint8 unchanged); detection
recognises Qwen3/DeepSeek shapes.

Findings: F-P3-FABLE-02, F-P3-FABLE-08.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik

Copy link
Copy Markdown
Collaborator Author

Heads-up: this branch now also carries the full-project review Wave 0 + Wave 1/H1 fixes

Beyond this PR's original HodeTech-migration scope, development now includes the
first two milestones of the 2026-06-10 full-project review action plan — 18
findings resolved across 13 commits, each a focused work-package shipped with
its regression test in the same commit
. ruff check . is clean repo-wide and
the full self-review gauntlet is green (2005 tests + 13 guards, incl. the
newly-wired check_audit_event_catalog).

Wave 0 — 7 CRITICAL release-blockers ✅

WP Commit Fix Finding
C1 425f24e GRPO eval_strategy reconciliation (no crash on validation split) XP-01
C4 6ffb9fb webhook persist-field drift → no approve/reject AttributeError crash XP-03
C2 2c65abd auto-reverted gated stage → exit 3, awaiting_approval discriminator XP-02
C3 595da5b refuse --resume-from across an unapproved gated stage (Art. 14) P2-FAB-01
C6 2dc052e verify_audit_log fails on truncate-to-empty vs genesis manifest P4-OPUS-01
C7 5f4ca56 audit-event-catalog guard fixed + wired into CI + tested XP-04 / F-P8-C-01
C5 838fd9f model-card redaction of synthetic.api_key (+ any secret) P1-FAB-01

Wave 1 / H1 — Trainer/model dispatch (11/11) ✅

Commit Fixes Findings
97ba469 model.max_length to all trainers; judge env-unset fails loud F-P3-FABLE-03, 18
08af249 MoE detect Qwen3/DeepSeek; unsloth lora.method; non-CUDA 4-bit warn; VLM pad guard F-P3-FABLE-07, 09, 10, 11
8756f2c TIES quantile→kthvalue (default merge works on real models) F-P3-FABLE-19
d2d1ebf OOM batch-size self-documented in manifest + exception-safe restore F-P3-FABLE-04
311b70a drop unused mergekit dep; align 6 doc/packaging surfaces F-P3-FABLE-20
a71b7cf MoE expert selection + quantization applied AFTER get_peft_model F-P3-FABLE-02, 08

Still to come (separate follow-up work)

W1 H2–H11 (~60 findings) and W2–W4 (~120 findings). Tracker:
docs/analysis/code_reviews/2026-06-10_2343_full_project_review/ACTION_PLAN.md
(gitignored working memory).

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/reference/audit_event_catalog.md`:
- Line 41: The documentation for the `pipeline.stage_completed` event
incorrectly lists `auto_revert_triggered` as an unconditional field in the
payload. Since the implementation in `forgelm/cli/_pipeline.py` only emits this
field on failure paths (not on success), update the payload documentation to
mark `auto_revert_triggered` as conditional or optional, clearly indicating it
is only present when pipeline stages fail. This ensures the audit event catalog
accurately reflects what the actual code emits.

In `@docs/standards/architecture.md`:
- Line 130: The documentation in docs/standards/architecture.md at line 130
states that heavy dependencies are declared with "specific version bounds," but
pyproject.toml does not enforce upper bounds for all of them—wandb, unsloth, and
deepspeed have only lower bounds. Update the wording to either use "version
constraints" as a more general term that covers both bounded and lower-bounded
cases, or explicitly call out that some dependencies have only lower bounds
while others have specific bounds. This will align the architectural standard
documentation with the actual dependency declarations in pyproject.toml.

In `@forgelm/cli/_pipeline.py`:
- Around line 390-400: The resume gate that checks for prior_state.status ==
"gated_pending_approval" refuses the operation by logging an error and returning
EXIT_CONFIG_ERROR, but fails to emit a structured audit event to the append-only
audit log before returning. Add a structured audit event emission before the
return statement that captures this decision gate event with at least three
pieces of information: the requested stage (resume_from parameter), the blocking
stage (prior_state.name), and the blocking status (the "gated_pending_approval"
status value). This ensures the append-only audit log has a record of the
blocked resume attempt as required by Article 14 decision gate guidelines.

In `@forgelm/cli/subcommands/_approve.py`:
- Around line 492-499: The webhook notification error handling is logging the
raw exception object directly, which can expose sensitive data like tokens or
URLs in webhook-related exceptions. For both the approve command notification
error handler at lines 492-499 (after the notifier.notify_success call) and the
similar reject command notification error handler at lines 572-577 (after what
is likely a notifier.notify_failure call), replace the exception object in the
logger.warning call with a generic message or exception type name that does not
include the raw exception details. This prevents leaking secret-bearing
information while still logging that the webhook notification failed.

In `@forgelm/model.py`:
- Around line 524-533: The code at line 524 attempts to extract target_modules
using nested getattr calls that fail when peft_config is a dictionary (keyed by
adapter name per PEFT's multi-adapter design), causing it to default to "?"
instead of showing actionable target_modules information. Modify the
target_modules extraction logic to first check if peft_config is a dictionary,
and if so, retrieve the first adapter's configuration object before accessing
the target_modules attribute. This ensures the warning message displays the
actual target_modules information regardless of whether peft_config is a simple
object or a multi-adapter dictionary.

In `@forgelm/trainer.py`:
- Around line 910-913: The `_revert_model()` function currently clears only
`train_result.staging_path` when reverting, but does not clear
`train_result.final_model_path`, which can point to a `gate_path` directory that
gets deleted. This causes the CLI to advertise a model path that no longer
exists. Set `train_result.final_model_path = None` alongside the existing
`train_result.staging_path = None` assignment in all three revert branches to
ensure the result does not report stale artifact paths.
- Around line 1402-1408: The ConfigError raised when judge_api_key_env is not
properly configured is currently being caught by generic Exception handlers in
two entrypoints and returning EXIT_TRAINING_ERROR (exit 2) instead of the
correct EXIT_CONFIG_ERROR (exit 1). Add dedicated exception handling for
ConfigError in forgelm/cli/_training.py (around lines 156-163) and
forgelm/cli/_pipeline.py (around lines 1295-1298), placing the ConfigError
handler before the generic Exception handler so that ConfigError exceptions are
caught specifically and return EXIT_CONFIG_ERROR to properly reflect the
configuration error exit code contract.

In `@tests/test_human_approval_gate.py`:
- Around line 216-217: The test does not properly validate the reset path for
the awaiting_approval flag because it starts with the default value of False.
Before executing the revert operation (around the TrainResult creation),
explicitly seed awaiting_approval to True in the test setup. This ensures that
when _apply_safety_result is called during revert, the test can properly verify
that the flag is actually being cleared rather than just remaining False by
default.

In `@tests/test_packaging.py`:
- Around line 120-125: The _load_optional_dependencies function currently only
attempts to import tomllib and skips the test if unavailable, but since the
project supports Python 3.10 where tomllib is not available, the test will
always skip on Python 3.10 despite having coverage available through tomli.
Align this function with the existing fallback pattern already present elsewhere
in the file (around lines 93-96) by attempting to import tomllib first, then
falling back to tomli if tomllib is not found, and only calling pytest.skip if
neither module is available. This ensures test coverage across all supported
Python versions including Python 3.10.

In `@tests/test_trainer_max_length.py`:
- Around line 72-86: The test_grpo_passes_max_length_as_prompt_cap function
lacks the importability guard that exists in the preference trainer test above
it. Before attempting to patch trl.GRPOConfig in the function, add a guard using
getattr(trl, "GRPOConfig") followed by a pytest.skip() call to gracefully skip
the test if trl.GRPOConfig cannot be imported due to torch/trl/vllm
compatibility issues, rather than allowing the test to hard-fail when the import
fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f5e1a2a-559e-4874-b4ff-1984b1d8fa46

📥 Commits

Reviewing files that changed from the base of the PR and between 009eec3 and a71b7cf.

📒 Files selected for processing (41)
  • .github/workflows/ci.yml
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • docs/reference/audit_event_catalog-tr.md
  • docs/reference/audit_event_catalog.md
  • docs/reference/usage-tr.md
  • docs/reference/usage.md
  • docs/standards/architecture.md
  • docs/usermanuals/en/reference/json-output.md
  • docs/usermanuals/tr/reference/json-output.md
  • forgelm/cli/_pipeline.py
  • forgelm/cli/_result.py
  • forgelm/cli/_training.py
  • forgelm/cli/subcommands/_approve.py
  • forgelm/cli/subcommands/_doctor.py
  • forgelm/compliance.py
  • forgelm/config.py
  • forgelm/merging.py
  • forgelm/model.py
  • forgelm/model_card.py
  • forgelm/results.py
  • forgelm/trainer.py
  • forgelm/webhook.py
  • pyproject.toml
  • tests/test_check_audit_event_catalog.py
  • tests/test_compliance.py
  • tests/test_cost_estimation.py
  • tests/test_human_approval_gate.py
  • tests/test_merging_algos.py
  • tests/test_model_card.py
  • tests/test_model_loading_dispatch.py
  • tests/test_moe_functions.py
  • tests/test_oom_recovery.py
  • tests/test_packaging.py
  • tests/test_pipeline_orchestrator.py
  • tests/test_trainer_grpo_config.py
  • tests/test_trainer_judge_env.py
  • tests/test_trainer_max_length.py
  • tests/test_webhook.py
  • tools/check_audit_event_catalog.py
✅ Files skipped from review due to trivial changes (3)
  • docs/reference/audit_event_catalog-tr.md
  • README.md
  • forgelm/config.py

Comment thread docs/reference/audit_event_catalog.md Resolved
Comment thread docs/standards/architecture.md Resolved
Comment thread forgelm/cli/_pipeline.py Resolved
Comment thread forgelm/cli/subcommands/_approve.py Resolved
Comment thread forgelm/model.py Resolved
Comment thread forgelm/trainer.py Resolved
Comment thread forgelm/trainer.py Resolved
Comment thread tests/test_human_approval_gate.py Resolved
Comment thread tests/test_packaging.py Resolved
Comment thread tests/test_trainer_max_length.py Resolved
…ert paths, audit/doc accuracy)

CI: `ruff format` config.py (the failing `ruff format --check .` gate).
Bandit B615 (from_pretrained without revision pinning) is left as-is — a
pre-existing MEDIUM-severity pattern shared by all four from_pretrained calls
(no revision pinning anywhere in the project); bandit fails only on HIGH.

Code:
- trainer.py: `_mark_reverted` clears `final_model_path` and `awaiting_approval`
  (not just `staging_path`) on every gate-revert branch — the revert deletes the
  model, so the CLI/JSON envelope must not advertise a stale path, and a reverted
  run is never awaiting approval.
- _training.py + _pipeline.py: route `ConfigError` to EXIT_CONFIG_ERROR (1) before
  the generic catch, so the judge-env-unset ConfigError (H1a) maps to the config-
  error exit code instead of EXIT_TRAINING_ERROR (2) in both entrypoints.
- _pipeline.py: emit a `pipeline.resume_refused` audit event (requested/blocking
  stage + status) before refusing a resume across an unapproved gate (Article 14
  decision-gate record) — catalogued EN+TR.
- model.py: resolve `peft_config` dict (PEFT keys by adapter name) before reading
  `target_modules` in the no-expert-adapters warning, so it shows the real value.
- _approve.py: webhook-failure handlers log the exception TYPE, not the raw object
  (a transport error can embed the secret-bearing URL/token).

Docs:
- audit_event_catalog (EN+TR): mark `stage_completed`'s `metrics` (success only)
  and `auto_revert_triggered` (failure path only) as conditional; add the new
  `pipeline.resume_refused` row.
- architecture standard: "specific version bounds" → "version constraints (a
  lower bound each; some also carry an upper bound)" to match pyproject.

Tests:
- ConfigError→exit-1 (pipeline stage + single-run CLI); resume-refusal emits the
  audit event; revert clears final_model_path + awaiting_approval (seeded True);
  target_modules surfaces from a dict peft_config; packaging tomli fallback for
  Python 3.10; GRPOConfig importability guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
forgelm/trainer.py (1)

1409-1415: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail this judge-secret validation before training starts.

This check currently runs only in the post-train judge stage, so a missing judge_api_key_env can burn the entire training job, write staged artifacts, and only then exit with a config error. Move the validation into _validate_evaluation_config() (or another preflight reached from __init__) and emit a dedicated audit event before raising so the refusal is fail-fast and recorded as its own decision, rather than only surfacing later via the generic pipeline.failed catch.

As per coding guidelines, every decision gate in forgelm/ must emit a structured audit event to the append-only audit log.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forgelm/trainer.py` around lines 1409 - 1415, The judge API key validation
check currently runs too late in the pipeline (post-train judge stage), allowing
the training job to proceed and consume resources before failing. Move the
validation block that checks judge_cfg.judge_api_key_env and raises ConfigError
into the _validate_evaluation_config() method (or another preflight validation
called from __init__) to fail fast before training begins. Additionally, emit a
structured audit event before raising the ConfigError to record this decision
gate, ensuring compliance with coding guidelines that require all decision gates
in forgelm/ to emit audit events to the append-only audit log.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@forgelm/trainer.py`:
- Around line 1409-1415: The judge API key validation check currently runs too
late in the pipeline (post-train judge stage), allowing the training job to
proceed and consume resources before failing. Move the validation block that
checks judge_cfg.judge_api_key_env and raises ConfigError into the
_validate_evaluation_config() method (or another preflight validation called
from __init__) to fail fast before training begins. Additionally, emit a
structured audit event before raising the ConfigError to record this decision
gate, ensuring compliance with coding guidelines that require all decision gates
in forgelm/ to emit audit events to the append-only audit log.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85f4e859-0619-4f07-9e2f-e6f2ad2f4cac

📥 Commits

Reviewing files that changed from the base of the PR and between a71b7cf and 5bc4f58.

📒 Files selected for processing (15)
  • docs/reference/audit_event_catalog-tr.md
  • docs/reference/audit_event_catalog.md
  • docs/standards/architecture.md
  • forgelm/cli/_pipeline.py
  • forgelm/cli/_training.py
  • forgelm/cli/subcommands/_approve.py
  • forgelm/config.py
  • forgelm/model.py
  • forgelm/trainer.py
  • tests/test_human_approval_gate.py
  • tests/test_moe_functions.py
  • tests/test_packaging.py
  • tests/test_pipeline_orchestrator.py
  • tests/test_trainer_judge_env.py
  • tests/test_trainer_max_length.py
✅ Files skipped from review due to trivial changes (3)
  • docs/standards/architecture.md
  • docs/reference/audit_event_catalog.md
  • forgelm/config.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • docs/reference/audit_event_catalog-tr.md
  • forgelm/cli/subcommands/_approve.py
  • tests/test_trainer_judge_env.py
  • tests/test_moe_functions.py
  • forgelm/cli/_pipeline.py
  • tests/test_packaging.py
  • tests/test_human_approval_gate.py
  • tests/test_trainer_max_length.py
  • tests/test_pipeline_orchestrator.py
  • forgelm/model.py

…fore training (review)

The judge_api_key_env check (F-P3-FABLE-18: an API judge whose env var is unset
must not silently flip to local mode) ran only at the post-train judge stage, so
a misconfigured run consumed the whole training loop before failing.

Move the check into a `_validate_judge_config` preflight called from `__init__`
(alongside `_validate_evaluation_config`), so construction raises ConfigError
before training starts. `_run_judge_if_configured` now calls the same shared
helper (defence-in-depth for the direct entry point), so there is no duplicated
logic and the existing judge-time tests stay valid. The CLI ConfigError→exit-1
routing already wraps trainer construction in both entrypoints, so the earlier
raise still maps to EXIT_CONFIG_ERROR.

Not done — the suggested audit event for this refusal is intentionally skipped:
a preflight config-validation error is not a runtime decision gate (the
audit-event requirement targets safety/judge/benchmark/approval gate *outcomes*
with scores); the sibling preflight `_validate_evaluation_config` likewise
raises/warns without audit events; and adding one would introduce a new catalogued
event for a non-gate config error before any run-level audit context exists. The
ConfigError message + the exit-1 contract are the actionable record.

Test: constructing ForgeTrainer with an unset judge_api_key_env raises ConfigError
at __init__ (preflight), not only at the judge stage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review all

@coderabbitai

coderabbitai Bot commented Jun 14, 2026 •

Copy link
Copy Markdown
Contributor

@cemililik Sure! Triggering another full review of all the changes in PR #61, including the HodeTech migration, the CVE ignore, and all the Wave 0 + Wave 1/H1 fixes you've described.

✅ Action performed

Full review finished.

@cemililik
cemililik merged commit 402bb05 into main Jun 14, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nightly CI failure — 2026-05-21

1 participant