Skip to content

Release v0.7.0 — Phase 14 multi-stage pipelines + SSRF hardening - #54

Merged
cemililik merged 23 commits into
mainfrom
development
May 15, 2026
Merged

cemililik merged 23 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented May 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Promotes the v0.7.0 release-prepared development branch to main. After
this merges, the next step is to tag v0.7.0 on main HEAD so
.github/workflows/publish.yml builds + publishes to PyPI.

Headline change: Phase 14 — multi-stage training pipelines. One YAML,
one CLI invocation, one Annex IV manifest for the whole chain.

Critical security fix: SSRF DNS-rebinding TOCTOU hardening on the
webhook / judge / synthetic outbound paths (issue #14).

What's in this release

Phase 14 — Multi-Stage Pipeline Chains (PR #53, 14 commits)

  • pipeline: config block chains 2+ training stages (SFT → DPO → GRPO,
    etc.) into one config-driven run.
  • Auto-chained model.name_or_path per stage; section-wholesale
    inheritance from the root for model / lora / training / data /
    evaluation; root-only enforcement for distributed / webhook /
    compliance / risk_assessment / monitoring / retention /
    synthetic / merge / auth.
  • New CLI flags: --stage, --resume-from, --force-resume,
    --input-model. Multi-stage --dry-run validation.
  • Chain-level compliance/pipeline_manifest.json (Annex IV) +
    forgelm verify-annex-iv --pipeline <dir> mode.
  • 7 new pipeline-scoped audit events, all sharing the pipeline run_id
    at the top level for SIEM grouping.
  • WebhookNotifier.notify_pipeline_started/_completed/_reverted.
  • Bilingual operator guide (docs/guides/pipeline.md + -tr.md),
    bilingual user-manual page (docs/usermanuals/{en,tr}/training/ pipelines.md), config template, README + features.html + index.html
    hero slide all updated to surface the feature.

Security — DNS-rebinding TOCTOU SSRF hardening (issue #14, PR #52)

  • _resolve_safe_destination resolves the hostname exactly once and
    rebuilds the outbound URL with the public IP literal so requests
    never re-resolves at connect time; Host header + SNI preserve
    virtual-hosted endpoints (Slack / Teams / Discord).
  • requests-toolbelt>=1.0.0,<2.0.0 is now a hard dependency.

5 review-response cycles on PR #53 addressed:

  • 2 blocking final-round findings (audit run_id pinning,
    --stage <non-first> chain integrity).
  • 1 significant (_atomic_write_json shared-tmp race → per-writer
    unique tmp).
  • 5 cognitive-complexity refactors (run() 53 → ≤15 and 4 sibling
    functions).
  • 14 nitpicks (mermaid label quoting, CHANGELOG event count, doc
    release-tense wording, topology-guard docstring, etc.).

Version mechanics

  • pyproject.toml: 0.6.1rc1 → 0.7.0
  • forgelm/_version.py: __api_version__ stays at 1.0.0 — Phase
    14 added symbols only inside forgelm.compliance /
    forgelm.config submodules; none re-exported in forgelm.__all__,
    so the public Python-library contract is unchanged.
  • site/*.html + site/js/translations.js: 19 version literals
    across 9 files re-derived from CHANGELOG via
    tools/update_site_version.py.

Backward compatibility

Single-stage configs (no pipeline: block) reach forgelm/trainer.py
byte-identical to v0.6.0; the orchestrator module is never
imported on the single-stage path. git diff a01308c~ -- forgelm/trainer.py is empty across the entire Phase 14 diff.

Test plan

  • forgelm --version prints ForgeLM 0.7.0
  • ruff format --check . + ruff check . clean
  • 387 / 387 release-critical tests pass locally (pipeline +
    integration + library API + compliance + verification toolbelt +
    CLI + trainer + config + bilingual parity + anchor resolution)
  • 8 / 8 doc guards green in the gauntlet (dry-run, site-version
    --check, bilingual parity, anchor resolution, CLI help
    consistency, wizard defaults, no-analysis-refs, no-unguarded-
    sys-modules-pop)
  • GitHub Actions CI green on development HEAD (4 Python versions)
  • After merge: tag v0.7.0 on main HEAD; publish.yml
    runs PyPI build + 12-cell OS/Python smoke matrix + SBOM emission
  • After PyPI publish: pip install forgelm==0.7.0 --force-reinstall
    then forgelm --version

Post-merge checklist

  1. git checkout main && git pull
  2. git tag -s v0.7.0 -m "v0.7.0 — Phase 14 multi-stage pipelines + SSRF hardening"
  3. git push origin v0.7.0 → triggers publish.yml
  4. Wait ~5-10 min, then verify on PyPI
  5. Reset development to next dev cycle: pyproject.toml →
    0.7.1rc1, CHANGELOG gets a new empty [Unreleased] placeholder
  6. (Optional) gh release create v0.7.0 --notes-from-tag for the
    GitHub Release page

Summary by CodeRabbit

  • New Features

    • Multi-stage pipelines: chain SFT → DPO → GRPO with auto-handoff, per-stage gates (human-approval/auto-revert), pipeline-scoped audit events and webhook notifications.
    • CLI: run/validate/resume per-stage with --stage, --resume-from, --force-resume, --input-model and multi-stage --dry-run.
    • Annex IV: chain-level pipeline manifest and verifier (verify-annex-iv --pipeline).
  • Documentation

    • Full pipeline guides (EN + TR), config template examples, site/hero and feature updates.

Review Change Stack

cemililik and others added 16 commits May 14, 2026 11:05
Phase 15 (Ingestion Pipeline Reliability) shipped under v0.6.0 on
2026-05-11; its detailed entry already lives at completed-phases.md
line 1281+ with the Wave 1 + Wave 2 + 5 review-absorption-round
summary.  The standalone roadmap/phase-15-ingestion-reliability.md
was a historical-reference duplicate — removed now that the
"completed" surface is the single source of truth.

Other drift fixed in the same sweep:

- docs/roadmap.md: Phase 14 release target v0.6.0 → v0.7.0 in the
  file-tree block (the table at the top already reflected this).
  Mermaid graph: P15 now flows into P14 (shipping order), P14's
  release node renamed V23 → V27 (v0.7.0), P15 carries the
  "✅ Released" suffix; mermaid styles flip P15 to the released
  green palette.  "Current state" paragraph: "19 phases under
  v0.5.5 / 3 phases planned" → "20 phases through v0.6.0 / 2
  phases planned"; "Phase 14 and Phase 15 ship in parallel under
  v0.6.0" → "Phase 14 is targeted for v0.7.0".

- docs/roadmap-tr.md: TR mirror of every roadmap.md edit above,
  plus the Phase 15 table row flipped from "Planlandı" to
  "✅ Tamam" with the link redirected at the completed-phases
  archive anchor.

- docs/guides/ingestion.md + ingestion-tr.md: the Wave 3 multi-
  column extraction reference pointed at the now-removed
  standalone doc; redirected to the completed-phases archive
  anchor.

- docs/roadmap/completed-phases.md: the Phase 15 "Related" block
  had a self-reference at the deleted standalone doc; replaced
  with the CHANGELOG section + PR #49 entry already adjacent.

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

The Phase 14 (Multi-Stage Pipeline Chains) doc had drifted from
reality after v0.6.0 shipped — Delivery still pointed at v0.6.0 and
the v0.5.5 closure cycle, Estimated Effort had an over-optimistic
3-week minimum, and every task carried high-level scope without
acceptance criteria or edge-case spec.  Phase 15's discipline
(acceptance criteria per task, fixture matrix, entry gate, deferred-
item ledger) is now mirrored here so implementation reviewers do
not have to derive the unstated edge cases mid-PR.

Sections refreshed (no behavioural code change — doc-only):

- **Status header.**  Added: planning, targeted for v0.7.0, no hard
  blockers; references the Phase 15 displacement event.
- **Estimated Effort + Priority.**  Retired the 3-week optimistic
  floor; called out that the risk surface is in orchestration
  semantics (inheritance merge, partial-run resume, gate
  composition), not trainer mechanics.
- **Task 1.**  Pipeline schema spec + stage-name regex + uniqueness
  validator + empty-list rejection + backward-compat assertion.
  Five acceptance bullets.
- **Task 2.**  Inheritance matrix as a table covering every top-
  level config section (11 rows) with explicit auto-chain /
  inherited / overridden / rejected-per-stage semantics and the
  "section-wholesale, not deep-merge" merge-depth rule called out
  with rationale.  Five acceptance bullets including a 44-test-case
  fixture matrix.
- **Task 3.**  Pipeline manifest JSON schema (illustrative,
  complete) + status enum + Annex IV mapping + chain integrity
  validator spec.  Per-stage training_manifest.json files retained
  alongside; pipeline manifest is an index, not a replacement.
- **Task 4.**  --dry-run failure-collection mode + --stage partial-
  run resolution table (5 scenarios) + --resume-from state machine
  + pipeline_state.json schema + stale-state guard with
  --force-resume override + --output-format json envelope spec.
- **Task 5.**  Per-stage gates composition + human-approval gate
  flow within a pipeline + 5 new audit-log events
  (pipeline.started / .stage_started / .stage_completed /
  .stage_reverted / .completed) + webhook notifier extension.
- **Requirements.**  Expanded from 4 bullets to 9 covering
  backward-compat byte-identical, dependency neutrality,
  determinism, bilingual docs sweep, wizard scope decision, test
  budget, honest limitations framing.
- **Test fixture sketch.**  6 fixtures under tests/fixtures/pipeline/
  modelled on Phase 15's matrix discipline.
- **Delivery.**  Rewritten: v0.7.0 target, entry gate (regression
  fixtures + GitHub issue + label + coverage baseline), CHANGELOG
  plan, validation gate, and a deferred-items ledger (intra-stage
  resume, DAG semantics, parallel execution, wizard integration).
- **Cross-references.**  Net new section: code surface +
  user-facing docs + standards consulted + pattern reference
  pointing at Phase 15's completed-phases.md anchor.

All 11 self-review gauntlet guards pass after the rewrite
(anchor resolution 270 md, bilingual parity 46 pairs, CLI help
consistency 447 invocations, wizard defaults, no-analysis-refs,
no-unguarded-sys.modules-pop, site version).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements every Phase 14 task documented in
``docs/roadmap/phase-14-pipeline-chains.md``.  Single-stage runs reach
``forgelm/trainer.py`` byte-identically to v0.6.0 — the orchestrator
module is only imported when a ``pipeline:`` block is present.

Foundation (Wave 1)

- ``forgelm/config.py``: ``PipelineStage`` + ``PipelineConfig`` Pydantic
  models enforce ``^[a-z0-9_]{1,32}$`` stage names, ≥ 1-stage
  pipelines, unique-name validator, and explicit-``trainer_type``
  audit-clarity rule per stage.  ``extra="forbid"`` rejects per-stage
  ``distributed`` / ``webhook`` / ``compliance`` / ``risk_assessment``
  / ``monitoring`` / ``retention`` / ``synthetic`` / ``merge`` /
  ``auth`` with the offending key in the Pydantic error.
- ``merge_pipeline_stage_config()`` helper materialises a flat
  ``ForgeConfig`` per stage with section-wholesale inheritance + the
  4-priority auto-chain resolution rule (``--input-model`` > explicit
  stage.model > prev.output_dir/final_model > root.model.name_or_path).
- ``ForgeConfig.pipeline: Optional[PipelineConfig]`` + ``model_rebuild()``
  to resolve the forward reference.

Orchestrator (Wave 2)

- ``forgelm/cli/_pipeline.py`` (new module): ``PipelineOrchestrator``
  drives the chain.  ``PipelineStageState`` + ``PipelineState``
  dataclasses round-trip to ``pipeline_state.json`` via atomic write
  (tmp + ``os.replace``).  ``pipeline.started`` /
  ``pipeline.stage_started`` / ``pipeline.stage_completed`` /
  ``pipeline.stage_reverted`` / ``pipeline.completed`` audit events
  fire alongside the existing per-stage ``training.*`` vocabulary so
  pre-existing dashboards keep working.
- ``--stage <name>`` partial-run requires the previous stage's
  on-disk output (auto-resolved) or ``--input-model <path>``
  (escape hatch); silent fallback to root model is hard-failed.
- ``--resume-from <name>`` skips ``completed`` stages whose
  output_model path exists.  Stale-state guard refuses to resume
  when ``pipeline_config_hash`` differs from the current YAML
  bytes; ``--force-resume`` bypasses with a WARNING + audit record.
- Human-approval gate inside a pipeline exits
  ``EXIT_AWAITING_APPROVAL (4)`` and leaves downstream stages
  ``pending`` (not ``skipped_due_to_prior_revert``) so resume
  picks them up post-approval.

Compliance (Wave 3)

- ``forgelm/compliance.py``: ``generate_pipeline_manifest()`` builds
  the Annex IV chain index documented in Phase 14 Task 3 schema.
  ``export_pipeline_manifest()`` writes it atomically.
  ``verify_pipeline_manifest()`` walks a pipeline run directory and
  returns a ``VerifyPipelineResult`` (chain integrity + per-stage
  manifest existence).  ``_verify_manifest_payload()`` is the
  in-memory helper for unit tests.
- ``forgelm verify-annex-iv --pipeline <run_dir>`` CLI mode validates
  a complete chain and exits non-zero on any violation.

CLI + webhook (Wave 4)

- ``forgelm/cli/_parser.py``: new ``--stage``, ``--resume-from``,
  ``--force-resume``, ``--input-model`` top-level flags.
- ``forgelm/cli/_dispatch.py``: routes to the orchestrator when
  ``config.pipeline`` is set; passes the raw YAML bytes for the
  config-hash guard.
- ``forgelm/webhook.py``: ``notify_pipeline_started`` /
  ``notify_pipeline_completed`` / ``notify_pipeline_reverted``
  methods; gated by the existing ``notify_on_start`` /
  ``notify_on_success`` / ``notify_on_failure`` config flags.

Tests + fixtures (Waves 2-5)

- ``tests/test_pipeline_config.py`` — 53 schema + inheritance-matrix
  + auto-chain priority tests.
- ``tests/test_pipeline_orchestrator.py`` — 27 state-machine tests
  covering happy-path / auto-revert / human-approval / resume /
  stale-config / partial-filter / webhook-failure-tolerance.
- ``tests/test_pipeline_compliance.py`` — 14 manifest schema +
  chain-integrity verifier tests (including partial-filter and
  auto-revert manifest acceptance).
- ``tests/test_pipeline_cli.py`` — 7 flag-interaction +
  dispatch-routing tests.
- 6 fixture YAMLs under ``tests/fixtures/pipeline/`` exercising
  every documented scenario.

Operator surface (Waves 6-7)

- ``docs/guides/pipeline.md`` + ``docs/guides/pipeline-tr.md`` —
  bilingual operator guide (when to use, inheritance matrix, CLI
  semantics, human-approval flow, Annex IV verifier, Limitations).
- ``config_template.yaml`` carries a commented-out ``pipeline:``
  example block.
- ``CHANGELOG.md`` ``[Unreleased]`` section ``Added (Phase 14 — …)``
  + ``Changed (Phase 14)`` entries.
- ``docs/roadmap/phase-14-pipeline-chains.md`` Status header
  updated to "Implementation complete on development; awaiting
  v0.7.0 PyPI tag".
- ``tools/check_bilingual_parity.py`` ``_PAIRS`` registers the new
  guide pair.

Wizard / notebooks / CLI help

- ``forgelm wizard`` is **not** extended with a pipeline path
  (operator-grade workflow; documented in the guide's Limitations).
- ``notebooks/`` carries no new pipeline notebook (fixture suite
  serves the same purpose without 3× boilerplate duplication).
- 451 ``forgelm`` invocations across docs / README pass the
  CLI-help-consistency guard against the live parser surface.

Verification

- ruff format + check: clean across 174 files.
- 11 / 11 self-review gauntlet guards green (dry-run, bilingual
  parity, anchor resolution, CLI help consistency 451
  invocations, wizard defaults, no-analysis-refs, no-unguarded-
  sys.modules-pop, site-version-literal sync).
- 1879 passed / 13 skipped / 0 failed across the full pytest
  suite (28 min 35 sec; ``tests/test_phase12_review_fixes.py``
  perf-flake remains documented out-of-scope).
- All 4 new Phase 14 test modules: 101 / 101 pass in 11 sec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses two review cycles on the Phase 14 pipeline-chains branch.

Blocking (Review 1):
- B-1 dispatch order: pipeline branch now runs before _maybe_run_no_train_mode
  so --dry-run reaches the orchestrator instead of the single-stage validator.
- B-2 strong-reference revert: stale state-file paths from prior runs no
  longer leak into the resume flow; hash mismatch now hard-aborts with a
  precise error unless --force-resume is given.
- B-3 chain integrity: _verify_manifest_payload now compares each stage
  against its immediate predecessor rather than the most-recent-with-output,
  so a hash mismatch on stage N cannot be masked by stage N-2's output.

Significant:
- F-1 leaf-vs-parent dir: replaced os.path.dirname(...) with
  os.path.isdir(stage_cfg.model.name_or_path) — the parent check was
  always true for any non-root path.
- F-2 empty --input-model: '' is normalised to None at dispatch time so
  the orchestrator never sees a falsy override that the truthy guard
  would silently treat as 'no override'.
- F-3 --force-resume audit trail: force path now emits a dedicated
  pipeline.force_resume event with both old + new pipeline_config_hash
  before mutating state. Mismatch path without --force returns a
  precise diagnostic string rather than raising.
- S-2 finalised-manifest verifier: flags a manifest that still has
  running stages as a structural integrity failure.
- S-4 atomic state writes: pipeline_state.json writes go through a
  tmp + os.replace round-trip to survive crashes mid-write.
- R1-S4 tamper guard: _load_state_file catches KeyError/TypeError/
  ValueError from _deserialise_state and treats a structurally invalid
  state file as 'no resume state' instead of crashing.

Nitpicks:
- N-1 docstring drift on PipelineStage.training (clarified that
  trainer_type is required when training: is supplied).
- N-2 verifier flags running stages on finalised manifest.
- N-3 audit-write failures are non-fatal — pipeline continues, warning
  is logged, regression test added.
- N-5 _emit_summary JSON is round-trippable (regression test added).
- N-6 gated stages emit a dedicated pipeline.stage_gated audit event
  (separate from .stage_completed).
- N-7 inheritance-matrix 'data' row marked 'strongly discouraged' in
  both EN + TR guides and the roadmap doc.
- N-9 release-time follow-up callout added to roadmap doc.
- N-10 force-resume test now asserts both old + new hashes in the audit
  payload, not just one.

N-8 (test helper deduplication into tests/_helpers/) deferred to a
follow-up PR — review explicitly labeled it 'Skip — follow-up PR'.

Verification:
- pytest tests/test_pipeline_*.py → 115 passed
- ruff format + ruff check → clean
- 11-guard gauntlet → all green (dry-run, bilingual parity, anchors,
  wizard defaults, no-analysis-refs, sys.modules guard, site version,
  CLI-help consistency, field descriptions, pip-audit, bandit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new findings from gemini-code-assist on top of the 0d58f1b batch
+ a small hardening tweak. All three appear in the PR as inline review
comments on forgelm/cli/_pipeline.py.

F-G-1 — output_dir collision check in dry-run (medium):
  Two stages resolving to the same training.output_dir silently overwrite
  each other's per-stage manifests + checkpoints. The footgun is real
  for stages that inherit the root training block wholesale. dry_run()
  now flags the collision (both explicit-override and inherited cases)
  via EXIT_CONFIG_ERROR before any GPU work.

F-G-2 — AttributeError on tampered state (medium):
  Round out the _load_state_file shape-tamper guard with AttributeError
  alongside KeyError / TypeError / ValueError. Covers the case where a
  hand-crafted stages: [null, "foo"] state file makes s.items() raise
  AttributeError on the non-dict item.

F-G-3 — chain-existence leaf check:
  Already addressed in 0d58f1b (F-1); re-confirmed here.

Tests added:
- test_dry_run_flags_output_dir_collision — explicit collision via two
  stage training blocks with the same output_dir.
- test_dry_run_flags_inherited_output_dir_collision — two stages that
  both inherit the root training.output_dir (no per-stage override).

CHANGELOG: extended the review-response section with the F-N-1
pipeline.stage_gated event (also new in this round) and the F-G-1
collision guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the Codacy "2 new issues" gate and the SonarCloud "Quality Gate
failed (E Reliability Rating on New Code)" gate on the Phase 14 PR.

BLOCKER bugs (SonarCloud python:S930 — root cause of the Reliability
Rating regression):
- forgelm/webhook.py _send: pipeline notifiers (notify_pipeline_started/
  completed/reverted) were passing stage_count / final_status /
  stopped_at / stage_name as keyword arguments to a fixed-signature
  _send method.  TypeError raised at runtime, swallowed by the pipeline
  orchestrator's best-effort try/except → pipeline webhooks silently
  never fired in production.
  _send now accepts **extra and merges them into the payload (with a
  base-field-collision guard so the contract stays stable).

MAJOR bugs:
- tests/test_pipeline_compliance.py _three_stage_state: removed dead
  `"completed" if all_completed else "completed"` ternary that always
  returned the same value (SonarCloud python:S3923).  Dropped the unused
  `all_completed` kwarg too.
- tests/test_pipeline_config.py: switched a float == 1.5 assertion to
  pytest.approx() (SonarCloud python:S1244).

MAJOR code smells:
- forgelm/cli/_pipeline.py _run_single_stage merge-failure branch:
  swapped logger.error(... exc) for logger.exception(...) (Sonar S8572)
  so the traceback lands in the log alongside the error message.
- Trainer-crash catch: pulled the long inline noqa rationale out into a
  preceding comment block so the # noqa: BLE001 marker has nothing
  trailing it (Sonar python:S7632 disliked the trailing — em-dash
  prose).

Codacy + minor smells:
- tests/test_pipeline_orchestrator.py: round = _deserialise_state(...)
  shadowed the builtin; renamed to round_tripped.
- tests/test_pipeline_compliance.py: renamed test_*is_OK to test_*is_ok
  so the method name matches the standard snake_case regex.
- tests/test_pipeline_cli.py _ns: dict(...) constructor → {} literal.

Verification:
- ruff format + ruff check clean.
- 149/149 pipeline + webhook tests pass.
- All 11 self-review guards green.

Cognitive-complexity CRITICAL code smells (run() at 53, _verify_*
at 24, etc.) are deliberately not addressed in this commit — they are
Maintainability-rating items and do not affect the failing
Reliability Rating gate, and refactoring them safely without
behavioural drift would warrant its own PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the 5 remaining CRITICAL code-smell findings on the Phase 14
PR by extracting helper methods.  Behaviour is preserved end-to-end —
the 227-test sweep (pipeline + CLI + verify-annex-iv + webhook +
library API) passes byte-identical to b3c6a45.

forgelm/cli/_dispatch.py:166 _main_inner (17 → ≤15):
  Extracted _dispatch_legacy_data_audit (the --data-audit alias path
  with its DeprecationWarning + audit-log breadcrumb + delegation to
  _run_data_audit) and _dispatch_pipeline_mode (the YAML re-read +
  run_pipeline_from_args dispatch).  _main_inner is now a thin
  argparse-then-route entry point.

forgelm/cli/_pipeline.py:289 run() (53 → ≤15):
  This was the biggest one.  Split into six helpers, each with one
  cohesive responsibility:
  - _prepare_resume_or_init_state — loads existing state for resume
    or builds a fresh one.  Returns (state, None) on success or
    (None, EXIT_CONFIG_ERROR) for invalid resume paths.
  - _resolve_resume_skiplist — walks state.stages before the resume
    point and builds the "already-completed on disk" skip list.
  - _resolve_stage_filter_setup — validates --stage <name> + seeds
    the auto-chain prev_output when filtering to a non-first stage.
  - _execute_one_stage — per-stage setup (started_at / status /
    trainer_type / save) + delegates to _run_single_stage + records
    duration.
  - _handle_stage_outcome — translates the per-stage exit code into
    the next-state transition (gated / completed / failed) +
    audit/webhook event + chain_broken flag.
  - _finalise_pipeline — phase 5+6: terminal state + pipeline.completed.

  The retained loop is just dispatch + skip/done bookkeeping; the
  branching that drove the cognitive-complexity score lives in
  the helpers, each at single-digit complexity.

forgelm/cli/_pipeline.py:821 _run_single_stage (18 → ≤15):
  Extracted:
  - _resolve_input_source — pure function returning the
    InputSourceLiteral via the documented priority ladder (CLI
    override > stage explicit > chain > root).
  - _merge_and_validate_stage — merge_pipeline_stage_config wrap +
    chain-presence guard.  Returns the merged ForgeConfig or None
    on a config-error path (caller maps to EXIT_CONFIG_ERROR).
  - _invoke_trainer — heavy-import-guarded ForgeTrainer lifecycle.
    Returns the TrainResult or None on a runtime/import failure.

  _run_single_stage is now linear: resolve source → merge → audit
  start → invoke trainer → record results.

forgelm/cli/subcommands/_verify_annex_iv.py:188 (27 → ≤15):
  Extracted:
  - _run_pipeline_mode — --pipeline flag branch (manifest + chain-
    integrity violations).
  - _verify_artefact_and_handle_io_errors — the FileNotFoundError /
    IsADirectoryError / JSONDecodeError / OSError → exit-code
    mapping that handles the single-artefact path's I/O failures.
  - _print_artefact_result — text + JSON output formatting.

  _run_verify_annex_iv_cmd is now: validate path → branch on mode →
  delegate.

forgelm/compliance.py:1057 _verify_manifest_payload (24 → ≤15):
  Lifted the per-stage chain-integrity check into _check_chain_link
  (pure function, idx-based for "compare against immediate
  predecessor") and the stopped_at + running-stage finalised checks
  into _check_status_consistency.  Top-level keys list lifted to
  module-level constant _PIPELINE_MANIFEST_REQUIRED_KEYS.

All 11 self-review guards remain green; ruff format + check clean;
227 / 227 tests pass on the broader pipeline + CLI sweep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each finding was first verified against current code; fixes are
minimal and validated.  All 232 broader-sweep tests pass; 11/11
self-review guards green; ruff format + check clean.

1) CHANGELOG.md — contradictory stage-count wording (valid):
   "2+ training stages" + "≥1-stage minimum" said two different
   things.  Code enforces ``min_length=1``.  Unified the bullet to
   "one or more training stages" + "at least 1 stage (`min_length=1`
   — …)" with the schema's nuance preserved (single-stage runs are
   better served by omitting the `pipeline:` block, but the schema
   accepts a 1-element pipeline for incremental authoring).

2) docs/guides/pipeline.md — release-tense wording (valid):
   v0.7.0 is still ``[Unreleased]`` in CHANGELOG; the guide read
   as already-shipped.  "added in `v0.7.0`" → "targeted for `v0.7.0`
   (currently on the `Unreleased` line of the CHANGELOG)".

3) docs/guides/pipeline-tr.md — release-tense wording (valid):
   Same fix in Turkish.  "`v0.7.0` ile geldi" → "`v0.7.0` için
   hedefleniyor (şu an CHANGELOG'un `Unreleased` satırında)".

4) forgelm/cli/_pipeline.py:_validate_resume_state — topology
   guard for `--force-resume` (valid):
   Even under --force-resume the old code accepted any stale state,
   so a renamed/inserted/deleted stage between runs would make
   ``state.stages[i]`` address a different stage than
   ``self.pipeline.stages[i]`` while the orchestrator iterates
   positionally.  Added a pre-emit guard that compares the ordered
   stage-name list (count + identifier + order); on mismatch we log
   + return an error string *before* the audit hook fires and
   before the stored hash is overwritten, so the JSONL audit trail
   doesn't carry a force_resume event for a refused resume.
   Regression test: test_force_resume_refuses_when_stage_topology_changed.

5) forgelm/cli/subcommands/_verify_annex_iv.py:_run_pipeline_mode —
   I/O-vs-config exit-code mapping (valid):
   ``verify_pipeline_manifest_at_path`` already encodes I/O failures
   as ``"… unreadable: …"`` sentinel strings, but the CLI mapped
   every non-empty violation list to ``EXIT_CONFIG_ERROR (1)``,
   contradicting the single-artefact path's policy
   (FileNotFoundError → 1, OSError → 2 via
   _verify_artefact_and_handle_io_errors).  Now:
   - try/except wraps the verifier call (defence-in-depth against
     a future change that lets exceptions bubble) and maps OSError
     → ``EXIT_TRAINING_ERROR (2)``.
   - The "unreadable" sentinel in the returned violations also maps
     to exit 2; everything else (including the "not found" sentinel
     and chain-integrity violations) stays at exit 1.
   Regression tests: TestVerifyAnnexIvPipelineModeExitCodes — three
   tests covering missing → 1, unreadable → 2, chain-violation → 1.

6) forgelm/compliance.py:_verify_manifest_payload +
   verify_pipeline_manifest_at_path — non-dict stage type-guard
   (valid):
   Both the in-memory verifier *and* the disk wrapper iterated
   manifest["stages"] and called ``s.get(...)`` without checking each
   ``s`` is a dict — a tampered manifest with ``stages: [null,
   "string"]`` would crash with AttributeError instead of producing
   a structured violation.  Added per-item ``isinstance(s, dict)``
   check in both code paths; the in-memory verifier filters the
   well-formed subset out and runs the rest of the checks on that
   filtered list so downstream helpers (which assume dicts) stay
   stable.  Regression test:
   test_disk_wrapper_type_guards_non_dict_stage_items pins both
   surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first node of the flowchart in docs/usermanuals/{en,tr}/training/lora.md
embedded a bare ``<`` inside the rhombus label:

    Q1{Need to fit on a<br/>consumer GPU<br/>(<24 GB)?}

Mermaid 10.9.4 parses the ``<`` as the start of an HTML tag inside a
``{...}`` rhombus node, fails to find a matching ``>``, and aborts the
whole diagram with "Syntax error in text".  The site's user-manual
renderer surfaces this verbatim where the diagram should be — the
exact error the user reported.

Fix: wrap the affected label in double quotes so Mermaid treats the
contents literally:

    Q1{"Need to fit on a<br/>consumer GPU<br/>(<24 GB)?"}

Same fix in the Turkish mirror.  No other mermaid label in the user
manuals uses an unquoted ``<`` — verified by grep across
docs/usermanuals/.

The site/js/usermanuals/*.js bundles are gitignored (regenerated at
CI build via ``tools/build_usermanuals.py``); ``build_usermanuals.py
--check`` is green so the next deploy picks the fix up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Following the LoRA decision-diagram fix (6a6ca60), a scan of every
mermaid block under docs/usermanuals/, docs/guides/, docs/reference/,
docs/qms/, docs/standards/, and docs/roadmap*/ surfaced 5 more node
labels with unquoted special characters that could trip Mermaid's
parser depending on version.  Fix in all of them: wrap the label in
``"…"`` so Mermaid takes the contents literally — same approach as
the LoRA fix.

Identified by a regex scanner that walks each mermaid fence, parses
out the shape brackets, strips safe ``<br/>`` tokens, and flags any
remaining bare ``<`` / ``>`` / ``&`` in the label.  Scanner re-runs
clean after these fixes.

Files:

- docs/usermanuals/en/getting-started/introduction.md:35
  ``I[Export &<br/>deploy]`` — bare ``&`` immediately before
  ``<br/>`` reads as a partial HTML entity start.  TR mirror uses
  "ve" instead of "&" and was already safe.
- docs/usermanuals/{en,tr}/concepts/alignment-overview.md:13
  ``SFT[SFT<br/>format & content]`` (EN) and
  ``SFT[SFT<br/>format & içerik]`` (TR) — same bare-``&`` risk.
- docs/reference/data_ingestion_architecture{,_tr}.md ~L210
  ``CHECK{>= 70 percent of pages?}`` (EN) and
  ``CHECK{>= yüzde 70 sayfa?}`` (TR) — bare ``>=`` at the start of a
  rhombus label can confuse Mermaid because ``>`` overlaps with the
  asymmetric-shape syntax.
- docs/roadmap/completed-phases.md:425
  ``F{> threshold?}`` — same bare-``>`` issue.

User-manual bundles regenerated via ``tools/build_usermanuals.py``
(the site/js/usermanuals/*.js bundles are gitignored — CI rebuilds
on deploy).  Bilingual parity + anchor-resolution guards green.

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

Two findings from the latest Sonar pass on a65d3c9:

forgelm/cli/_pipeline.py:550 — run() cognitive complexity 22 → ≤15
  After the previous refactor (a65d3c9, 53 → ≤15) the for-loop body
  still contributed ~7 to the score: three nested skip checks, two
  post-execution conditional updates, and one ``should_break`` guard
  all sat inside a for inside the method.

  Lifted the entire stage loop into ``_run_stage_loop`` and the
  skip-decision into ``_stage_skip_reason`` (returns a discriminated
  string for "filtered" / "already_completed" / "chain_broken" /
  None).  Inline the two ``if x: y = z`` post-execution updates as
  ternary / boolean-or expressions:

    prev_output = new_prev_output if new_prev_output is not None else prev_output
    chain_broken = chain_broken or chain_broken_now

  ``run()`` is now linear: prepare state → resume skiplist → filter
  setup → started event → loop → finalise → summary.

tests/test_pipeline_compliance.py:496 — try/except SystemExit (S5754)
  The ``_run`` helper in TestVerifyAnnexIvPipelineModeExitCodes wraps
  ``_run_verify_annex_iv_cmd`` in a bare ``try/except SystemExit`` to
  capture the CLI's exit code.  Sonar flagged the catch as the
  "swallow without reraise" anti-pattern (BaseException subclass —
  ``SystemExit`` inherits from BaseException, not Exception).

  Replaced with ``pytest.raises(SystemExit)`` — pytest's idiomatic
  context manager for CLI tests.  The contract is now positive
  ("we *expect* SystemExit every invocation") rather than
  speculative-catch, and a future code path that fails to ``sys.exit``
  would surface as a test failure rather than being silently masked.

Verification:
- ruff format + check clean
- 154 / 154 pipeline + webhook tests pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent final-review reviewers spotted 6 findings; both were
verified against HEAD before any change.  All 6 are addressed.

🔴 F-B-1 (Blocking) — Pipeline audit events got different run_ids
   Verified: AuditLogger.__init__ accepts an optional run_id; without
   one it auto-generates ``fg-<uuid>``.  ``_audit_event`` constructed
   a fresh AuditLogger per call, so every entry in the pipeline-level
   ``audit_log.jsonl`` carried a different top-level ``run_id`` field
   even though the events shared the same ``pipeline_run_id`` payload.
   That breaks SIEM-style grouping and the Article 12 record-keeping
   correlation contract — auditors join on ``run_id``.

   Fix: cache an AuditLogger on the orchestrator, pinned to the
   pipeline run id, lazily initialised on the first event.
   ``_audit_event`` now requires ``pipeline_run_id`` as a keyword-only
   argument (all 8 existing call sites already pass it, so the new
   signature is back-compat-safe and prevents silent regression).
   Regression test: test_audit_events_share_one_pipeline_run_id.

🔴 F-B-2 (Blocking) — --stage <non-first> manifests fail the chain verifier
   Verified: ``--stage dpo_stage`` with the predecessor's
   ``final_model`` on disk runs correctly (auto-chains from the
   on-disk seed) but the predecessor's manifest entry has
   ``status: skipped_by_filter`` and ``output_model: null``.  The
   executed stage records ``input_source: "chain"``, so the strict
   chain verifier (F-B-3 from an earlier cycle) compares against
   ``stages[X-1].output_model = null`` and reports
   ``chain_integrity_violation: previous stage has no output_model``
   on every legitimate ``--stage <non-first>`` run.

   Fix: after ``_resolve_stage_filter_setup`` returns the resolved
   disk seed, surface it on the predecessor's
   ``state.stages[filter_idx - 1].output_model`` so the manifest
   accurately represents "stage X-1 produced this path (in a prior
   run); stage X chained from it now".  Strict chain verifier is
   happy; stage X-1's status stays ``skipped_by_filter`` to convey
   "we didn't run it in this invocation".  Regression test:
   test_filter_middle_stage_with_disk_seed_passes_chain_verifier.

🟡 F-S-1 (Significant) — Pipeline state writes raced on shared .tmp
   Verified: ``_atomic_write_json`` used ``f"{path}.tmp"`` —
   shared filename per target.  Two writers truncate each other's
   tmp and lose the race to ``os.replace`` with FileNotFoundError.
   Phase 14 explicitly doesn't support concurrent orchestrators on
   the same ``pipeline.output_dir`` (documented now under
   Limitations), but the FileNotFoundError traceback was a worse
   failure mode than last-writer-wins.

   Fix: switch to ``f"{path}.{pid}.{rand}.tmp"`` for the temp name.
   Each writer gets its own tmp file, atomically replaces.  Added a
   defensive cleanup on write failure (orphan tmp removed; original
   exception re-raised).  The temp lives in the same directory as
   the target so ``os.replace`` stays a same-filesystem atomic
   rename.

🟢 F-N-1 (Nitpick) — CHANGELOG audit-event count was stale
   The Added section said "5 new audit events" enumerating the
   original five; HEAD emits 7 (added stage_gated in F-N-1 and
   force_resume in F-B-2 from earlier cycles).  Updated the count
   + list; also called out the F-B-1 run_id-pinning fix.

🟢 F-N-2 (Nitpick) — Limitations doc gaps
   docs/guides/pipeline.md + pipeline-tr.md gained three new bullets:
   - No multi-process locking on pipeline.output_dir (concurrent
     orchestrators race; pick distinct dirs).
   - Stage-vs-pipeline output_dir overlap is allowed but interleaves
     the audit log (matches the inheritance_matrix.yaml fixture's
     layout — documented, not a bug).
   - --force-resume topology guard catches positional breaks only;
     value-level edits (trainer_type, output_dir, hyperparameters
     under identical names) are accepted by design.

🟢 F-N-3 (Nitpick) — _validate_resume_state docstring clarity
   The topology-guard comment now spells out that value-level edits
   are accepted by design — the operator's explicit signal under
   ``--force-resume`` that they understand the divergence.  Aligns
   the code's contract with the guide's Limitations bullet.

Verification:
- ruff format + check clean
- 159 / 159 pipeline + webhook tests pass (new audit-run_id +
  --stage-disk-seed tests added)
- All 11 self-review guards green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A single CRITICAL python:S5754 finding on cd9f416's new atomic-write
race regression test: ``except BaseException`` is the documented
anti-pattern (SystemExit / KeyboardInterrupt should propagate).

Narrowed to ``except Exception`` — still wide enough to capture any
unexpected escape from ``_atomic_write_json`` for the race-probe
assertion, but doesn't shadow SystemExit / KeyboardInterrupt the way
``BaseException`` does.  The race the test actually targets is an
OSError (FileNotFoundError from ``os.replace`` losing to a concurrent
unlink), well within ``Exception``.  Test still passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(phase-14): multi-stage training pipeline chains (SFT → DPO → GRPO)
…ng site

Phase 14 multi-stage pipelines (PR #53) merged into development without
the promotional / discoverability surface around it. This commit lands
that surface so operators looking for "chain my trainers together"
actually find the feature.

Scope (single content drop — no behavioural changes):

1. README.md — added "Multi-Stage Pipelines" row to the Guides table
   between Alignment and CI/CD Pipeline Integration, with EN + TR
   guide links.

2. site/features.html — added a "Multi-Stage Pipelines" feature card
   in the Training & Alignment group (between GRPO and QLoRA), with a
   chain-icon SVG and short copy: "Chain SFT → DPO → GRPO in one
   config-driven run. Auto-chained inputs, per-stage gates,
   crash-safe resume, one Annex IV manifest covering the whole chain."

3. site/js/translations.js — new `features.train.pipeline.{title,body}`
   i18n keys in all 6 languages (EN/TR/DE/FR/ES/ZH) for the card.

4. site/index.html — new 4th hero slide (after compliance) using new
   `home.hero.pipeline.*` keys. Title "SFT → DPO → GRPO, one config.",
   CTAs to the pipeline guide + YAML example.  Slider auto-detects
   the new slide (DOM-driven in main.js); new pagination dot added
   with localised aria-label.

5. site/js/translations.js — new `home.hero.pipeline.{title,lead,
   cta1,cta2}` + `home.hero.dot.pipeline` i18n keys in all 6 languages.
   Note: pre-existing orphan `home.pipeline.*` keys (trainer-stack
   pitch from a prior section) are left intact; they describe a
   different concept (six paradigms) and a separate marketing pass
   can decide whether to wire or remove them.

6. docs/usermanuals/{en,tr}/training/pipelines.md — new sidebar page
   under Training & Alignment, mirroring the structure of dpo.md /
   grpo.md (mermaid decision tree, minimal config, CLI surface,
   what-the-orchestrator-gives-you, inheritance matrix, limitations,
   see-also). Sources of truth remain `docs/guides/pipeline{,-tr}.md`;
   the manual page is the discovery surface inside the sidebar.

7. docs/usermanuals/_meta.yaml — registered the new page (`id:
   pipelines`) between `grpo` and `lora` in the Training & Alignment
   section, with localised titles in all 6 languages.

8. docs/usermanuals/{en,tr}/concepts/choosing-trainer.md — added a
   short note under "Common starting points" pointing every "→"-shaped
   trainer sequence at the new pipelines page, plus a "See also" entry.

9. site/js/usermanuals/{en,tr,_index}.js — regenerated via
   tools/build_usermanuals.py (66 pages per language, up from 65).

Validation (all green):
- ruff format + check
- 11-guard gauntlet (dry-run, bilingual parity, anchor resolution,
  CLI help consistency, wizard defaults, no-analysis-refs,
  unguarded-sys-modules-pop, site-version, build_usermanuals --check)
- 180 / 180 tests across pipeline + bilingual-parity + anchor-resolution

Out of scope (deferred):
- CHANGELOG move from [Unreleased] → [0.7.0] — release will be cut
  after this surface refresh is approved.
- Wizard (CLI + web) pipeline support — Phase 14 explicit out-of-scope.
- Old `home.pipeline.*` orphan i18n keys — separate marketing pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finalises the [Unreleased] → [0.7.0] — 2026-05-14 cut on top of the
Phase 14 multi-stage pipelines content drop.

Version mechanics:
- pyproject.toml: 0.6.1rc1 → 0.7.0
- forgelm/_version.py: __api_version__ stays at 1.0.0 — Phase 14 added
  symbols only inside forgelm.compliance / forgelm.config submodules;
  none re-exported in forgelm.__all__, so the public Python API
  contract is unchanged.
- CHANGELOG: dated header + release-summary paragraph above the
  pre-existing Added / Changed / Fixed / Security blocks.  New
  [Unreleased] placeholder anticipates the 0.7.1 dev cycle.
- site/ version literals (9 files, 19 substitutions) re-derived from
  CHANGELOG's latest released header via tools/update_site_version.py:
  JSON-LD softwareVersion on every page, hero badge HTML fallback in
  index.html, pip-install pins on quickstart.html, and home.hero.badge
  i18n strings across 6 locales in translations.js.

Verification:
- forgelm --version reports `ForgeLM 0.7.0`
- ruff format + check clean
- 387 / 387 release-critical tests pass (pipeline + integration +
  library API + compliance + verification toolbelt + CLI + trainer +
  config + bilingual parity + anchor resolution)
- 8 / 8 doc guards in the gauntlet green:
  dry-run, site-version --check (now reads 0.7.0), bilingual parity,
  anchor resolution, CLI help consistency, wizard defaults,
  no-analysis-refs, no-unguarded-sys-modules-pop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @cemililik, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented May 14, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@cemililik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 21 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0e3bebe-36e5-43ff-b472-eb6d564ac8a3

📥 Commits

Reviewing files that changed from the base of the PR and between d3ed8cd and 846a460.

📒 Files selected for processing (7)
  • .github/workflows/nightly.yml
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • forgelm/cli/_dispatch.py
  • forgelm/cli/_pipeline.py
  • tests/test_pipeline_cli.py
  • tests/test_pipeline_orchestrator.py
📝 Walkthrough

Walkthrough

Implements Phase 14 multi-stage pipeline chains: adds pipeline: config schema and merge logic, a PipelineOrchestrator with resume/dry-run/state persistence and audit/webhook hooks, Annex IV chain manifest generation/verification, CLI flags/dispatch changes, extensive docs and fixtures, and tests; bumps project version to 0.7.0.

Changes

Phase 14: Multi-Stage Pipelines

Layer / File(s) Summary
Config schema and merge
forgelm/config.py
Adds PipelineConfig and PipelineStage Pydantic types, ForgeConfig.pipeline field, validators (stage-name regex, no duplicate names, required training.trainer_type when stage provides training), and merge_pipeline_stage_config to produce per-stage ForgeConfig honoring auto-chain and CLI overrides.
Orchestrator implementation
forgelm/cli/_pipeline.py
New PipelineOrchestrator, PipelineState/PipelineStageState models, run-id/config-hash helpers, atomic state/manifest write, stage loop with gating/auto-revert/human-approval semantics, dry_run(), resume logic, and run_pipeline_from_args() CLI glue.
Compliance / Annex IV
forgelm/compliance.py
Generate/export pipeline-level compliance/pipeline_manifest.json; in-memory and on-disk verification helpers enforcing stage index ordering, chain integrity (prev output == input when input_source=="chain"), and disk checks for per-stage training_manifest.json.
CLI parser, dispatch, verifier
forgelm/cli/_parser.py, forgelm/cli/_dispatch.py, forgelm/cli/subcommands/_verify_annex_iv.py
Adds pipeline CLI flags (--stage,--resume-from,--force-resume,--input-model), fast-fail checks for pipeline-only flags, centralizes legacy --data-audit handling, dispatches pipeline configs to run_pipeline_from_args(), and extends verify-annex-iv with --pipeline mode.
Webhook notifier
forgelm/webhook.py
WebhookNotifier._send(..., **extra) accepts pipeline-specific fields with collision guard; adds notify_pipeline_started, notify_pipeline_completed, notify_pipeline_reverted; updates exports.
Docs, guides, site, README, version
docs/guides/pipeline.md, docs/guides/pipeline-tr.md, docs/usermanuals/*, CHANGELOG.md, README.md, site/*, pyproject.toml
New EN/TR pipeline guides, usage/config reference updates, roadmap adjustments, CHANGELOG entry and Unreleased placeholder, README guide link, site hero/translation additions; project version bumped to 0.7.0.
Fixtures & tests
tests/fixtures/pipeline/*, tests/test_pipeline_*.py, tests/test_check_module_size.py, tests/test_cli_subcommands.py
New fixtures covering inheritance, stale-state, gating, auto-revert; test suites for config, orchestrator, CLI dispatch, compliance verifier, and module-size/test expectation updates.
Tools & linting
tools/check_bilingual_parity.py, tools/check_module_size.py, .markdownlint.json
Register EN/TR pipeline doc pairs, grandfather forgelm/cli/_pipeline.py in module-size checks, and disable MD025 in markdownlint config.

Sequence Diagram

sequenceDiagram
  participant CLI
  participant Orchestrator
  participant ForgeTrainer
  participant Compliance
  participant Webhook
  CLI->>Orchestrator: run_pipeline_from_args(args, pipeline_yaml_bytes)
  Orchestrator->>Orchestrator: load/init / compute run_id & config_hash
  Orchestrator->>ForgeTrainer: execute stage (merged ForgeConfig)
  ForgeTrainer-->>Orchestrator: result (exit_code, metrics, manifests)
  Orchestrator->>Compliance: export_pipeline_manifest(pipeline_state, root_cfg)
  Orchestrator->>Webhook: notify_pipeline_started / notify_pipeline_completed / notify_pipeline_reverted
  Orchestrator-->>CLI: final exit code / JSON/text summary
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • cemililik/ForgeLM#53: Implements Phase 14 multi-stage pipeline: support end-to-end with matching orchestrator, compliance, audit, and CLI semantics.
  • cemililik/ForgeLM#19: Related webhook/payload typing and notifier changes that this PR extends for pipeline notifications.
  • cemililik/ForgeLM#39: Related tooling change updating bilingual parity pairs; this PR adds pipeline doc mirrors to the same registry.

Poem

🐰 I dug a YAML path so neat,
Stages hop forward, outputs meet.
States are saved, no carrot lost,
Audits sing — webhook post!
Hooray, the pipeline's complete.

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

@codacy-production

codacy-production Bot commented May 14, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 448 complexity · 28 duplication

Metric Results
Complexity 448
Duplication 28

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements Phase 14, introducing multi-stage training pipeline chains that allow operators to sequence multiple training paradigms (like SFT, DPO, and GRPO) using a single YAML configuration. The implementation includes a new PipelineOrchestrator module that manages stage transitions, auto-chains model inputs, and ensures crash-safe resumes via an atomic state file. Additionally, it introduces chain-level Annex IV compliance manifests, dedicated audit events, and webhook notifications, while maintaining full backward compatibility for single-stage runs. Feedback recommends explicitly clearing the CUDA cache after each stage to prevent GPU memory fragmentation and potential out-of-memory errors during sequential model loading.

Comment thread forgelm/cli/_pipeline.py
dataset = prepare_dataset(stage_cfg, tokenizer)
trainer = ForgeTrainer(model=model, tokenizer=tokenizer, config=stage_cfg, dataset=dataset)
return trainer.train()
except Exception as exc: # noqa: BLE001

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In multi-stage pipelines, loading heavy LLM models and datasets sequentially can lead to GPU memory fragmentation or OOM errors if the previous stage's memory isn't fully reclaimed. It is recommended to explicitly clear the CUDA cache after each stage's trainer.train() call to ensure the next stage has maximum available memory.

            result = trainer.train()
            try:
                import torch
                if torch.cuda.is_available():
                    torch.cuda.empty_cache()
            except ImportError:
                pass
            return result

@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: 7

🧹 Nitpick comments (2)
docs/usermanuals/en/training/lora.md (1)

16-18: 💤 Low value

Consider quoting Q2 and Q3 node labels for consistency.

Line 16 now quotes the Q1 label (likely to fix Mermaid rendering with the <br/> HTML tag), but Q2 and Q3 on lines 17-18 also contain <br/> tags and remain unquoted. For consistent Mermaid syntax and parsing behavior, consider quoting all decision-node labels that contain HTML or special characters.

✨ Proposed consistency fix
     Q1{"Need to fit on a<br/>consumer GPU<br/>(<24 GB)?"}
-    Q2{Want full-precision<br/>weights kept frozen?}
-    Q3{Need maximum<br/>quality at LoRA cost?}
+    Q2{"Want full-precision<br/>weights kept frozen?"}
+    Q3{"Need maximum<br/>quality at LoRA cost?"}
🤖 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/training/lora.md` around lines 16 - 18, The Q2 and Q3
decision-node labels in the Mermaid snippet are not quoted even though they
contain HTML <br/> tags like Q1; update the labels Q2 and Q3 (alongside Q1) to
be quoted strings so all nodes with HTML or special characters use consistent
quoted labels (e.g., change Q2{...} and Q3{...} to "Q2"{...} and "Q3"{...} or
otherwise wrap their label text in quotes) to ensure consistent Mermaid parsing.
docs/usermanuals/tr/training/lora.md (1)

16-18: 💤 Low value

Consider quoting Q2 and Q3 node labels for consistency.

Line 16 now quotes the Q1 label (likely to fix Mermaid rendering with the <br/> HTML tag), but Q2 and Q3 on lines 17-18 also contain <br/> tags and remain unquoted. For consistent Mermaid syntax and parsing behavior, consider quoting all decision-node labels that contain HTML or special characters.

✨ Proposed consistency fix
     Q1{"Consumer GPU'da<br/>(<24 GB) sığmalı mı?"}
-    Q2{Full-precision<br/>ağırlıklar dondurulsun mu?}
-    Q3{LoRA maliyetinde<br/>maksimum kalite mi?}
+    Q2{"Full-precision<br/>ağırlıklar dondurulsun mu?"}
+    Q3{"LoRA maliyetinde<br/>maksimum kalite mi?"}
🤖 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/tr/training/lora.md` around lines 16 - 18, Quote the Q2 and
Q3 decision-node labels like Q1 so all nodes containing HTML/special characters
are consistent; update the labels Q2{"Full-precision<br/>ağırlıklar dondurulsun
mu?"} and Q3{"LoRA maliyetinde<br/>maksimum kalite mi?"} to include surrounding
quotes (matching the Q1 pattern) to ensure uniform Mermaid node parsing.
🤖 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 `@CHANGELOG.md`:
- Around line 9-10: The changelog header [0.7.0] was added but the footer link
references still stop at [0.6.0] and [Unreleased] still references v0.6.0;
update the footer link block so [Unreleased] compares from v0.7.0 (not v0.6.0)
and add a new link entry for [0.7.0] that compares the prior tag to v0.7.0
(e.g., the compare range from v0.6.0..v0.7.0), ensuring the link labels
[Unreleased], [0.7.0] and any remaining version entries are consistent with the
new header.

In `@docs/usermanuals/en/concepts/choosing-trainer.md`:
- Around line 84-85: Update the Turkish (TR) mirror of the user manual to match
the new English guidance added in choosing-trainer.md: replicate the sentence
advising users to chain "→"-shaped rows with a pipeline: config block instead of
separate YAMLs and include the same link reference to Multi-Stage Pipelines;
ensure the TR page contains the equivalent phrasing and link so EN and TR docs
remain in parity across the choosing-trainer content and the referenced
Multi-Stage Pipelines guidance.

In `@forgelm/cli/_dispatch.py`:
- Around line 187-190: The deprecation message string containing "`forgelm
--data-audit PATH`" in forgelm/cli/_dispatch.py still says the flag "will be
removed in v0.7.0" even though this PR is the v0.7.0 cut; update that
deprecation text (the string literal referencing --data-audit) to either bump
the removal target (e.g., "will be removed in v0.8.0") or remove the
removal-timeline clause entirely so the message is not misleading.

In `@forgelm/cli/_pipeline.py`:
- Around line 1055-1057: The audit emission except block currently swallows
failures (except Exception as exc) and only logs a warning; change it so
audit-write failures fail the pipeline: replace the warning-only behavior in the
audit emission except block (the line using logger.warning and variables event
and exc) with code that logs the error (logger.error(..., exc_info=exc) or
equivalent) and then re-raises the exception (or raise a Pipeline/Audit-specific
exception) so the run exits non-success rather than continuing silently; ensure
the new behavior is applied where the audit emission is performed (the
try/except around emitting audit events) so the pipeline does not continue on
audit-write failure.

In `@forgelm/cli/subcommands/_verify_annex_iv.py`:
- Around line 238-261: The exit-code selection currently inspects message text
via unreadable = any("unreadable" in v for v in violations), which is brittle;
change the verifier to return structured results (e.g., each violation as an
object with an error_kind field or an enum like
ErrorKind.UNREADABLE/CONFIG/TRAINING) and update this CLI logic to base exit
mapping on that field instead of substring checks: iterate violations (or a
top-level result.error_kind) to determine whether any error_kind == UNREADABLE
(map to EXIT_TRAINING_ERROR) or other kinds (map to EXIT_CONFIG_ERROR,
EXIT_EVAL_FAILURE, etc.) and keep the existing JSON/plain output formatting
(output_format, violations) but emit structured violation objects when JSON
selected and use the EXIT_* constants (EXIT_SUCCESS, EXIT_CONFIG_ERROR,
EXIT_TRAINING_ERROR, etc.) accordingly.

In `@site/index.html`:
- Around line 201-203: The CTA anchor uses href="features.html#training" but the
target page has no matching id, so fix by either updating the fragment in this
anchor to the actual id used on the features page (replace
"features.html#training" with the correct fragment) or add id="training" to the
corresponding target element on the features page; locate the anchor by the href
value and the span with data-i18n="home.hero.pipeline.cta1" to make the change.

In `@tests/fixtures/pipeline/inheritance_matrix.yaml`:
- Around line 37-39: The Stage-1 config currently defines a top-level training:
block (with trainer_type: "sft") which overrides instead of inheriting defaults;
remove the training: block from Stage-1 so it inherits training settings from
the defaults (or if a single field override was intended, replace the whole
block with only the specific override mechanism used by the pipeline so
inheritance merges rather than replaces).

---

Nitpick comments:
In `@docs/usermanuals/en/training/lora.md`:
- Around line 16-18: The Q2 and Q3 decision-node labels in the Mermaid snippet
are not quoted even though they contain HTML <br/> tags like Q1; update the
labels Q2 and Q3 (alongside Q1) to be quoted strings so all nodes with HTML or
special characters use consistent quoted labels (e.g., change Q2{...} and
Q3{...} to "Q2"{...} and "Q3"{...} or otherwise wrap their label text in quotes)
to ensure consistent Mermaid parsing.

In `@docs/usermanuals/tr/training/lora.md`:
- Around line 16-18: Quote the Q2 and Q3 decision-node labels like Q1 so all
nodes containing HTML/special characters are consistent; update the labels
Q2{"Full-precision<br/>ağırlıklar dondurulsun mu?"} and Q3{"LoRA
maliyetinde<br/>maksimum kalite mi?"} to include surrounding quotes (matching
the Q1 pattern) to ensure uniform Mermaid node parsing.
🪄 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: 886ed9bb-e44f-4a72-b257-de540eda2fd1

📥 Commits

Reviewing files that changed from the base of the PR and between ccb7d84 and 9f81265.

📒 Files selected for processing (53)
  • CHANGELOG.md
  • README.md
  • config_template.yaml
  • docs/guides/ingestion-tr.md
  • docs/guides/ingestion.md
  • docs/guides/pipeline-tr.md
  • docs/guides/pipeline.md
  • docs/reference/data_ingestion_architecture-tr.md
  • docs/reference/data_ingestion_architecture.md
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • docs/roadmap/completed-phases.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • docs/roadmap/phase-15-ingestion-reliability.md
  • docs/usermanuals/_meta.yaml
  • docs/usermanuals/en/concepts/alignment-overview.md
  • docs/usermanuals/en/concepts/choosing-trainer.md
  • docs/usermanuals/en/getting-started/introduction.md
  • docs/usermanuals/en/training/lora.md
  • docs/usermanuals/en/training/pipelines.md
  • docs/usermanuals/tr/concepts/alignment-overview.md
  • docs/usermanuals/tr/concepts/choosing-trainer.md
  • docs/usermanuals/tr/training/lora.md
  • docs/usermanuals/tr/training/pipelines.md
  • forgelm/cli/_dispatch.py
  • forgelm/cli/_parser.py
  • forgelm/cli/_pipeline.py
  • forgelm/cli/subcommands/_verify_annex_iv.py
  • forgelm/compliance.py
  • forgelm/config.py
  • forgelm/webhook.py
  • pyproject.toml
  • site/compliance.html
  • site/contact.html
  • site/features.html
  • site/guide.html
  • site/index.html
  • site/js/translations.js
  • site/privacy.html
  • site/quickstart.html
  • site/terms.html
  • tests/fixtures/pipeline/auto_revert_at_stage_2.yaml
  • tests/fixtures/pipeline/gated_pending_approval.yaml
  • tests/fixtures/pipeline/inheritance_matrix.yaml
  • tests/fixtures/pipeline/invalid_distributed_per_stage.yaml
  • tests/fixtures/pipeline/minimal_3_stage.yaml
  • tests/fixtures/pipeline/stale_state_resume_v1.yaml
  • tests/fixtures/pipeline/stale_state_resume_v2.yaml
  • tests/test_pipeline_cli.py
  • tests/test_pipeline_compliance.py
  • tests/test_pipeline_config.py
  • tests/test_pipeline_orchestrator.py
  • tools/check_bilingual_parity.py
💤 Files with no reviewable changes (1)
  • docs/roadmap/phase-15-ingestion-reliability.md

Comment thread CHANGELOG.md
Comment on lines +84 to +85
For any "→"-shaped row above, chain the stages with a `pipeline:` config block instead of writing two YAMLs and orchestrating manually. See [Multi-Stage Pipelines](#/training/pipelines).

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mirror this new guidance in the TR user manual page.

These additions update user-facing guidance in EN only; the corresponding TR mirror should be updated in lockstep to keep documentation parity.

As per coding guidelines "User-facing docs are bilingual (EN + TR mirrors)".

Also applies to: 104-104

🤖 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/concepts/choosing-trainer.md` around lines 84 - 85,
Update the Turkish (TR) mirror of the user manual to match the new English
guidance added in choosing-trainer.md: replicate the sentence advising users to
chain "→"-shaped rows with a pipeline: config block instead of separate YAMLs
and include the same link reference to Multi-Stage Pipelines; ensure the TR page
contains the equivalent phrasing and link so EN and TR docs remain in parity
across the choosing-trainer content and the referenced Multi-Stage Pipelines
guidance.

Comment thread forgelm/cli/_dispatch.py
Comment thread forgelm/cli/_pipeline.py
Comment on lines +1055 to +1057
except Exception as exc: # noqa: BLE001 — audit emission is best-effort telemetry; a write failure must not abort the pipeline.
logger.warning("Failed to emit audit event %r: %s", event, exc)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not swallow pipeline audit-write failures.

Line 1055 currently treats audit emission as best-effort and continues on failure. This can silently break the compliance trail for a run. In CLI pipeline execution, audit failures should fail the run (or be explicitly mapped to a non-success exit), not be warning-only.

Suggested fix
-        except Exception as exc:  # noqa: BLE001 — audit emission is best-effort telemetry; a write failure must not abort the pipeline.
-            logger.warning("Failed to emit audit event %r: %s", event, exc)
+        except Exception as exc:  # noqa: BLE001
+            raise RuntimeError(f"Failed to emit required audit event {event!r}: {exc}") from exc

As per coding guidelines: "No silent failures. Avoid bare except:, except Exception: pass, or exception swallowing except for explicitly-non-fatal paths like webhooks and cleanup".

🤖 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/cli/_pipeline.py` around lines 1055 - 1057, The audit emission except
block currently swallows failures (except Exception as exc) and only logs a
warning; change it so audit-write failures fail the pipeline: replace the
warning-only behavior in the audit emission except block (the line using
logger.warning and variables event and exc) with code that logs the error
(logger.error(..., exc_info=exc) or equivalent) and then re-raises the exception
(or raise a Pipeline/Audit-specific exception) so the run exits non-success
rather than continuing silently; ensure the new behavior is applied where the
audit emission is performed (the try/except around emitting audit events) so the
pipeline does not continue on audit-write failure.

Comment on lines +238 to +261
unreadable = any("unreadable" in v for v in violations)

if output_format == "json":
print(
json.dumps(
{
"success": not violations,
"mode": "pipeline",
"path": os.path.abspath(path),
"violations": violations,
},
indent=2,
)
)
# Round 6 absorption: the prior `os.path.isfile()` pre-check
# collapsed two distinct conditions ("path doesn't exist" and
# "path exists but the user can't stat it") into a single
# `EXIT_CONFIG_ERROR`, breaking the documented exit-code
# contract for the permission-denied case. Move all path
# validation inside the try block and let Python's exception
# hierarchy disambiguate: FileNotFoundError + IsADirectoryError
# = caller-input error (exit 1); OSError (incl. PermissionError)
# = genuine I/O failure on a reachable path (exit 2).
elif not violations:
print(f"OK: pipeline manifest at {path}")
else:
print(f"FAIL: pipeline manifest at {path}")
for v in violations:
print(f" - {v}")

if not violations:
sys.exit(EXIT_SUCCESS)
sys.exit(EXIT_TRAINING_ERROR if unreadable else EXIT_CONFIG_ERROR)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Exit-code mapping is brittle because it depends on message text parsing.

unreadable = any("unreadable" in v for v in violations) makes EXIT_CONFIG_ERROR vs EXIT_TRAINING_ERROR depend on verifier wording. Any message change/casing change can silently break the CLI contract for automation users.

Please switch to a structured verifier result (e.g., error_kind enum) and map exit codes from that instead of substring checks.

As per coding guidelines, “Exit codes are a public contract: 0=success, 1=config, 2=training, 3=eval-failure, 4=awaiting-approval, 5=wizard-cancelled.”

🤖 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/cli/subcommands/_verify_annex_iv.py` around lines 238 - 261, The
exit-code selection currently inspects message text via unreadable =
any("unreadable" in v for v in violations), which is brittle; change the
verifier to return structured results (e.g., each violation as an object with an
error_kind field or an enum like ErrorKind.UNREADABLE/CONFIG/TRAINING) and
update this CLI logic to base exit mapping on that field instead of substring
checks: iterate violations (or a top-level result.error_kind) to determine
whether any error_kind == UNREADABLE (map to EXIT_TRAINING_ERROR) or other kinds
(map to EXIT_CONFIG_ERROR, EXIT_EVAL_FAILURE, etc.) and keep the existing
JSON/plain output formatting (output_format, violations) but emit structured
violation objects when JSON selected and use the EXIT_* constants (EXIT_SUCCESS,
EXIT_CONFIG_ERROR, EXIT_TRAINING_ERROR, etc.) accordingly.

Comment thread site/index.html
Comment on lines +201 to +203
<a href="features.html#training" class="btn btn-primary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h6a3 3 0 0 1 3 3v8a3 3 0 0 0 3 3h6"/><circle cx="3" cy="5" r="1.5"/><circle cx="12" cy="11" r="1.5"/><circle cx="21" cy="19" r="1.5"/></svg>
<span data-i18n="home.hero.pipeline.cta1">Read the pipeline guide</span>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the pipeline CTA target fragment.

Line 201 links to features.html#training, but the target page does not define a matching id="training", so the jump target is broken.

🤖 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 `@site/index.html` around lines 201 - 203, The CTA anchor uses
href="features.html#training" but the target page has no matching id, so fix by
either updating the fragment in this anchor to the actual id used on the
features page (replace "features.html#training" with the correct fragment) or
add id="training" to the corresponding target element on the features page;
locate the anchor by the href value and the span with
data-i18n="home.hero.pipeline.cta1" to make the change.

Comment thread tests/fixtures/pipeline/inheritance_matrix.yaml Outdated
Each finding from the v0.7.0 release-review pass (PR #54) was verified
against current code; fixes are minimal.  3 findings skipped with
reasons below.

CI BLOCKER unblocked
---
test (3.10/3.11/3.12/3.13) failures: forgelm/cli/_pipeline.py grew to
1063 LOC (over the 1000-LOC warn ceiling) during Phase 14's 5
review-response cycles and is not on the grandfather allowlist.
Phase 14's pipeline orchestrator is one cohesive state machine; a
clean sub-package split (`_pipeline/{__init__,_state,_events,
_verify}.py`) is the right destination but doesn't belong in the
v0.7.0 cut PR.  Added to `_GRANDFATHERED_OVER_CEILING` in
`tools/check_module_size.py` with an inline comment pinning the
follow-up target (v0.7.x, alongside the Phase 15 audit-package
split pattern).  `tests/test_check_module_size.py` updated for the
new entry count (7 → 8).

Real bugs (6)
---
- CHANGELOG.md footer: `[Unreleased]` compared against v0.6.0 even
  though we just released v0.7.0; added `[0.7.0]` compare entry and
  updated `[Unreleased]` base.
- forgelm/cli/_dispatch.py: the `--data-audit` deprecation message
  said the flag "will be removed in v0.7.0" but we are AT v0.7.0
  and the flag is not removed.  Pushed the removal target to v0.8.0
  to preserve the one-minor warning window for operators who only
  upgrade once per minor (per
  docs/standards/release.md#deprecation-cadence).  Audit-log
  `version="v0.7.0 removal"` field updated to match.
- site/index.html: 4th hero slide's primary CTA pointed at
  `features.html#training` but the target page had no element with
  `id="training"`.  Added `id="training"` to the Training feature
  group's <section> element on features.html.
- tests/fixtures/pipeline/inheritance_matrix.yaml: Stage 1's comment
  said "training inherited" but the YAML supplied
  `training: { trainer_type: "sft" }`, which under Phase 14's
  section-wholesale semantics REPLACES the root training block
  (losing output_dir, learning_rate, num_train_epochs).  Removed the
  per-stage training: block so the documented inheritance actually
  happens; updated the comment to spell out what the wholesale-
  inheritance path now provides.
- docs/roadmap/phase-14-pipeline-chains.md: heading hierarchy
  skipped H1 → H3 (codacy MD001).  Promoted Tasks + Requirements +
  Test fixture + Delivery + Cross-references + Release-time follow-up
  from H3 → H2 (they are top-level siblings, not children).
- docs/usermanuals/{en,tr}/training/pipelines.md: Codacy flagged
  MD025 (multiple top-level headings) because each manual page has
  both a YAML frontmatter `title:` AND a body `# Heading`.  Every
  page under docs/usermanuals/ follows this pattern by design (the
  frontmatter title feeds sidebar nav + SPA page title, the body H1
  is the visible page heading).  Added MD025 to the disabled-rules
  block in .markdownlint.json with the rationale.

Nitpicks (2)
---
- docs/usermanuals/{en,tr}/training/lora.md: Q2 and Q3 mermaid
  decision-node labels not quoted even though Q1 was.  Quoted them
  for consistency — same `<br/>`-inside-label pattern as Q1.

Skipped with reason (3)
---
- forgelm/cli/_pipeline.py:1055-1057 — re-raise audit-emission
  failures: SKIP.  Contradicts the documented best-effort design at
  docs/standards/error-handling.md "Best-effort artefact carve-out":
  audit emission is explicitly non-fatal, alongside webhooks and
  cleanup.  A read-only disk or transient I/O failure on the audit
  log would currently bring down an in-progress training run —
  strictly worse than missing one audit breadcrumb that is
  reconstructible from `pipeline_state.json` + `pipeline_manifest.
  json`.
- forgelm/cli/subcommands/_verify_annex_iv.py:238-261 — substring →
  structured `error_kind` enum: SKIP for v0.7.0 cut.  Real refactor
  opportunity but out of scope for a release-prep PR; the substring
  check works correctly today and three regression tests in
  TestVerifyAnnexIvPipelineModeExitCodes pin the contract.  Filed
  as v0.7.x follow-up.
- forgelm/cli/_pipeline.py:1201-1204 — explicit CUDA cache clear
  between stages: SKIP for v0.7.0 cut.  Trainer instances + model
  references drop out of scope between stages so Python GC reclaims
  most memory; explicit `torch.cuda.empty_cache()` is best practice
  but needs PyTorch-conditional import + GPU-only guard.  Tracked
  for v0.7.x follow-up.

Verification
---
- ruff format --check + ruff check clean
- 237 / 237 tests pass across pipeline + module-size + bilingual
  parity + anchor resolution
- `python3 tools/check_module_size.py --strict` exits 0 (the CI
  blocker)
- 11 / 11 doc guards green in the gauntlet (dry-run, site-version
  --check, bilingual parity, anchor resolution, CLI help
  consistency, build_usermanuals --check)

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

Copy link
Copy Markdown
Collaborator Author

Review response — commit a54dc16

Each of the 8 inline review comments below was verified against current code, then either fixed or explicitly skipped with a reason. All 10 fixes from this round are bundled in commit a54dc16.

✅ Fixed (6)

Comment File Fix
coderabbitai — CHANGELOG.md:10 — footer compare links CHANGELOG.md Added [0.7.0]: v0.6.0...v0.7.0, updated [Unreleased]: v0.7.0...HEAD
coderabbitai — _dispatch.py:192 — deprecation says "v0.7.0" but we are v0.7.0 forgelm/cli/_dispatch.py Bumped removal target to v0.8.0 (preserves one-minor warning window per docs/standards/release.md); audit-log version field updated to match
coderabbitai — site/index.html:203 — features.html#training broken fragment site/features.html Added id="training" to the Training feature-group <section>
coderabbitai — inheritance_matrix.yaml — Stage-1 wholesale-overrides instead of inheriting tests/fixtures/pipeline/inheritance_matrix.yaml Dropped the per-stage training: block; comment now spells out what wholesale-inheritance from the root provides
codacy — phase-14-pipeline-chains.md:15 — H1 → H3 heading skip docs/roadmap/phase-14-pipeline-chains.md Promoted Tasks + Requirements + Test fixture + Delivery + Cross-references + Release-time follow-up from H3 → H2 (top-level siblings, not children)
codacy — pipelines.md:6 — multiple top-level headings (MD025) .markdownlint.json Disabled MD025 globally with a rationale comment — every docs/usermanuals/ page uses YAML frontmatter title: + body # Heading by design (sidebar nav + visible page heading), so the rule conflicts with established docs convention

✅ Verified (1)

  • coderabbitai — choosing-trainer.md TR mirror — verified: the TR file already had both the cross-link and the See-also entry from an earlier commit. No further action needed.

⏭️ Skipped with reason (3)

  • coderabbitai — _pipeline.py:1057 re-raise audit-emission failures — Contradicts the documented best-effort design at docs/standards/error-handling.md "Best-effort artefact carve-out": audit emission is explicitly non-fatal alongside webhooks and cleanup. A read-only disk or transient I/O failure would currently bring down an in-progress training run — strictly worse than missing one audit breadcrumb that is reconstructible from pipeline_state.json + pipeline_manifest.json.

  • coderabbitai — _verify_annex_iv.py:261 substring check → structured error_kind enum — Real refactor opportunity but out of scope for a release-prep PR. Substring check works correctly today; 3 regression tests in TestVerifyAnnexIvPipelineModeExitCodes pin the exit-code contract. Tracked for v0.7.x follow-up.

  • gemini-code-assist — _pipeline.py:1204 explicit CUDA cache clear between stages — Trainer instances + model references drop out of scope between stages, so Python GC reclaims most memory; explicit torch.cuda.empty_cache() is best practice but needs PyTorch-conditional import + GPU-only guard (Phase 14 supports CPU-only dry-run and CI environments). Tracked for v0.7.x follow-up.

🟢 Nitpicks fixed (2)

  • lora.md (EN+TR) Q2/Q3 mermaid label quoting — Quoted for consistency with Q1; same <br/>-inside-label pattern.

🔴 CI blocker unblocked (separate from the review comments)

The 4× test (3.10/3.11/3.12/3.13) failures were on tests/test_check_module_size.py::TestMainAtHead::test_strict_mode_at_head_is_green — forgelm/cli/_pipeline.py grew to 1063 LOC over the 1000-LOC warn ceiling during Phase 14's 5 review-response cycles and wasn't on the grandfather list. Added to _GRANDFATHERED_OVER_CEILING in tools/check_module_size.py with an inline comment pinning the v0.7.x sub-package split as the follow-up target; tests/test_check_module_size.py updated for the new entry count (7 → 8).

Verification

  • ruff format + check clean
  • 237 / 237 tests pass across pipeline + module-size + bilingual parity + anchor resolution
  • python3 tools/check_module_size.py --strict exits 0
  • All 11 doc guards green in the gauntlet

a54dc16 bumped the `--data-audit` deprecation removal target from
v0.7.0 to v0.8.0 in forgelm/cli/_dispatch.py (the v0.7.0 cut-release
review-response cycle, preserving the one-minor warning window per
docs/standards/release.md#deprecation-cadence), but the matching
audit-log assertion in test_legacy_data_audit_flag_emits_deprecation_
warning_and_audit_event still pinned "v0.7.0 removal".

CI failure on PR #54:
  AssertionError: assert 'v0.8.0 removal' == 'v0.7.0 removal'
  tests/test_cli_subcommands.py:175

Update the assertion to match the new value; added an inline comment
explaining where the version came from so a future deprecation-cadence
change knows to touch both the dispatch source and this test in the
same commit.

Co-Authored-By: Claude Opus 4.7 (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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
tests/test_check_module_size.py (1)

277-287: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the new grandfathered module to the parametrized test.

The @pytest.mark.parametrize list verifies that each grandfathered module exists in the tree, but it still contains only 7 entries while _GRANDFATHERED_OVER_CEILING now has 8. The new forgelm/cli/_pipeline.py module should be added to the parametrize list to ensure it's also verified to exist.

🧪 Proposed fix to add the missing parametrize entry
 `@pytest.mark.parametrize`(
     "rel_path",
     [
         "forgelm/compliance.py",
         "forgelm/trainer.py",
         "forgelm/ingestion.py",
         "forgelm/cli/subcommands/_purge.py",
         "forgelm/config.py",
         "forgelm/cli/_parser.py",
         "forgelm/cli/subcommands/_doctor.py",
+        "forgelm/cli/_pipeline.py",
     ],
 )
🤖 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 `@tests/test_check_module_size.py` around lines 277 - 287, The parametrize list
in tests/test_check_module_size.py for the "rel_path" pytest.mark.parametrize
currently lists seven grandfathered modules but _GRANDFATHERED_OVER_CEILING
contains eight; update the parametrize list used by the test (the "rel_path"
parameter in the decorator) to include the missing entry
"forgelm/cli/_pipeline.py" so the test verifies that module's presence as well.
🤖 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 `@CHANGELOG.md`:
- Around line 17-18: The CHANGELOG.md has a markdownlint MD018 error because the
"(issue" line breaks before `#14`, making `#14` look like a heading; update the
text in the CHANGELOG entry (the line containing "hardening for outbound webhook
/ judge / synthetic destinations (issue" and the `#14` token) so `#14` stays
inline with the preceding text (e.g., "... (issue `#14`)." or escape the `#` as
`\#14`) to prevent it being parsed as a heading.

In `@docs/roadmap/phase-14-pipeline-chains.md`:
- Around line 229-234: The "Entry gate (all must hold before the first Phase 14
PR opens):" heading and its bullets are written as if pending; update the tense
to reflect that the entry gate has already occurred—e.g., change the heading to
"Entry gate (all must have held prior to the first Phase 14 PR):" and convert
verbs in the bullets like "is opened" and "is created" to past tense ("was
opened", "was created") so the three bullets under "Entry gate" read as
completed actions (keep the same bullet content and the "CHANGELOG plan" text
unchanged).
- Around line 3-5: Remove the blank line between the two blockquote lines so the
note becomes a single contiguous blockquote: locate the two lines beginning with
"> **Status:**" and "> **Note:**" and delete the empty line separating them so
both lines remain prefixed with ">" and no intervening blank line exists.
- Around line 134-136: The fenced CLI block after the list item "4. [ ] **CLI
surface**" violates MD031 because it begins immediately after the list header;
fix by adding a blank line before the opening ```bash fence and a blank line
after the closing ``` fence so the code block is separated from the list item
text (ensure the block uses ```bash and contains the two lines shown, e.g., "#
End-to-end pipeline run" and "forgelm --config pipeline.yaml").

---

Outside diff comments:
In `@tests/test_check_module_size.py`:
- Around line 277-287: The parametrize list in tests/test_check_module_size.py
for the "rel_path" pytest.mark.parametrize currently lists seven grandfathered
modules but _GRANDFATHERED_OVER_CEILING contains eight; update the parametrize
list used by the test (the "rel_path" parameter in the decorator) to include the
missing entry "forgelm/cli/_pipeline.py" so the test verifies that module's
presence as well.
🪄 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: c9d97da4-5ae0-4664-af85-cfea5faf71ba

📥 Commits

Reviewing files that changed from the base of the PR and between 9f81265 and 277eebc.

📒 Files selected for processing (11)
  • .markdownlint.json
  • CHANGELOG.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • docs/usermanuals/en/training/lora.md
  • docs/usermanuals/tr/training/lora.md
  • forgelm/cli/_dispatch.py
  • site/features.html
  • tests/fixtures/pipeline/inheritance_matrix.yaml
  • tests/test_check_module_size.py
  • tests/test_cli_subcommands.py
  • tools/check_module_size.py
✅ Files skipped from review due to trivial changes (1)
  • tests/fixtures/pipeline/inheritance_matrix.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • site/features.html
  • forgelm/cli/_dispatch.py

Comment thread CHANGELOG.md Outdated
Comment thread docs/roadmap/phase-14-pipeline-chains.md
Comment thread docs/roadmap/phase-14-pipeline-chains.md
Comment thread docs/roadmap/phase-14-pipeline-chains.md Outdated
cemililik and others added 2 commits May 15, 2026 08:14
Each finding was verified against current code; all 5 were valid and
applied as minimal edits.  No skipped items in this round.

1. CHANGELOG.md ~L17 — MD018: ``(issue`` linebreak before ``#14``
   made ``#14)`` parseable as a heading.  Rewrote to keep the issue
   reference inline and converted to a clickable link
   ``[#14](https://github.com/cemililik/ForgeLM/issues/14)`` for the
   GitHub renderer.

2. docs/roadmap/phase-14-pipeline-chains.md L3-5 — two ``>``-prefixed
   blockquote lines were separated by a blank line, splitting them
   into two paragraphs.  Inserted a ``>`` continuation line between
   Status and Note so the renderer keeps them as one contiguous
   blockquote (the conventional pattern for multi-paragraph block
   quotes).

3. docs/roadmap/phase-14-pipeline-chains.md L229-234 — the Entry
   gate section's tense read as if Phase 14 was still upcoming
   ("all must hold before the first Phase 14 PR opens", "is opened",
   "is created").  Phase 14 has shipped; converted to past tense
   ("all must have held prior to ...", "was opened", "was created",
   "was measured").

4. docs/roadmap/phase-14-pipeline-chains.md L134 — MD031: a fenced
   ``\`\`\`bash`` block opened immediately on the line after
   ``4. [ ] **CLI surface**`` with no blank-line separation.  Added
   the blank line between list-item text and fence opener.  Closing
   fence already had a trailing blank line so MD031 is satisfied
   both sides.

5. tests/test_check_module_size.py L277-287 — the
   ``test_grandfathered_module_exists_in_tree`` parametrize list
   had 7 entries but ``_GRANDFATHERED_OVER_CEILING`` grew to 8 in
   a54dc16 (forgelm/cli/_pipeline.py).  Added the 8th entry with
   an inline comment pinning the lockstep relationship.

Verification:
- 80 / 80 module-size + dispatch-deprecation + doc-guard tests pass
- anchor resolution + bilingual parity green

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

Each finding was verified against current code; 10 were applied as
minimal edits, 4 documented as out-of-scope for the release-cut PR.

🔴 BLOCKER 1 — Pipeline dispatch silently overrode no-train flags
   File: forgelm/cli/_pipeline.py
   Verified: ``_dispatch_pipeline_mode`` routes BEFORE
   ``_maybe_run_no_train_mode`` (the F-B-1 fix from PR #53 was
   explicit about this), so ``--fit-check`` / ``--merge`` /
   ``--generate-data`` / ``--compliance-export`` / ``--benchmark-only``
   on a pipeline config would silently trigger a full pipeline
   training run instead of the single-stage operation requested.
   Fix: ``run_pipeline_from_args`` now rejects each of the five
   unsupported single-stage flags with EXIT_CONFIG_ERROR + an
   actionable error pointing operators at ``--dry-run`` for
   multi-stage validation.  ``--dry-run`` itself still routes to
   ``orchestrator.dry_run()``.  Regression test:
   ``test_no_train_single_stage_flags_rejected_on_pipeline_config``.

🔴 BLOCKER 2 — --resume-from did not skip gated_pending_approval
   File: forgelm/cli/_pipeline.py:376
   Verified: ``_resolve_resume_skiplist`` only skipped stages with
   ``status == "completed"``.  A stage that exited with
   EXIT_AWAITING_APPROVAL is recorded as ``gated_pending_approval``.
   After ``forgelm approve`` promotes the staging path and the
   operator resumes from a later stage, the gated SFT would re-train,
   DPO would auto-chain from the freshly-trained output instead of
   the operator-approved one, and the audit log would carry two
   ``pipeline.stage_started`` events for the same stage_index.
   Fix: skip set now contains both ``completed`` and
   ``gated_pending_approval`` — presence of an ``output_model``
   directory on disk is what proves the stage produced a real
   artefact, regardless of which status label it carries.
   Regression test:
   ``test_resume_skips_gated_pending_approval_with_on_disk_output``.

🔴 BLOCKER 3 — Topology guard skipped on hash match
   File: forgelm/cli/_pipeline.py:909
   Verified: ``_validate_resume_state`` returned ``None`` immediately
   when ``existing.pipeline_config_hash == self.config_hash``, before
   the topology check ran.  A tampered / truncated / hand-edited
   ``pipeline_state.json`` could carry the same config_hash (operator
   never edited the YAML) but a damaged ``stages[]`` array, and the
   resume would silently overwrite the wrong stage's history because
   ``state.stages[i]`` is positionally addressed.
   Fix: topology check (ordered ``[s.name for s in stages]`` lists)
   now runs UNCONDITIONALLY — before the hash shortcut.  Regression
   test: ``test_topology_guard_runs_even_on_hash_match``.

🟠 HIGH 4 — output_dir collision check only in dry_run()
   File: forgelm/cli/_pipeline.py:770
   Verified: the per-stage ``training.output_dir`` collision guard
   only fired during ``--dry-run``; an operator who skipped dry-run
   could silently overwrite per-stage manifests + checkpoints across
   stages.
   Fix: extracted the collision check into
   ``_check_output_dir_collisions`` and now call it as a pre-flight
   in ``run()`` too.  Regression test:
   ``test_output_dir_collision_rejected_in_run_not_only_dry_run``.

🟠 HIGH 5 — Pipeline-only flags silently ignored on non-pipeline configs
   File: forgelm/cli/_dispatch.py:300
   Verified: ``--stage`` / ``--resume-from`` / ``--force-resume`` /
   ``--input-model`` against a config with no ``pipeline:`` block
   was a no-op — operator surprise.
   Fix: ``_main_inner`` now rejects each pipeline-only flag with
   EXIT_CONFIG_ERROR + an actionable error.  Regression test:
   ``test_pipeline_only_flags_rejected_on_non_pipeline_config``.

🟠 HIGH 8 — Reference docs missing the pipeline: block + new CLI flags
   Files: docs/reference/configuration.md, configuration-tr.md,
          usage.md, usage-tr.md
   Verified: docs/standards/documentation.md mandates that config /
   CLI surface changes are mirrored in the canonical reference docs
   (EN + TR) before merge.  Phase 14's ``pipeline:`` block and the
   four new CLI flags were not yet documented.
   Fix: appended a "pipeline (Optional — Multi-Stage Training
   Chains, Phase 14)" section to ``configuration.md`` + TR mirror
   covering the schema (root + PipelineStage fields, inheritance
   rules, root-only sections, YAML example, CLI surface, verifier
   command).  Added a "Multi-Stage Pipelines (Phase 14, v0.7.0)"
   subsection to ``usage.md`` + TR mirror with the 7 CLI invocations
   and the single-stage-flag-rejection contract.  Cross-linked both
   directions.

🟠 HIGH 9 — Module-size grandfather list policy was a frozen v0.6.x set
   File: tools/check_module_size.py
   Verified: the file-level docstring claimed the grandfather list
   was specifically "the seven modules that already sit over the
   ceiling at PR #29 HEAD"; adding ``forgelm/cli/_pipeline.py`` to
   that list without acknowledging the policy change weakens the
   guard's discipline.
   Fix: rewrote the grandfather-policy section of the docstring to
   make the admission criteria explicit (inline-comment requirement
   + follow-up tracking artefact) and called out the post-PR-#29
   addition (``_pipeline.py``).  The discipline IS preserved — every
   entry has a tracking pointer.

🟡 MEDIUM 12 — JSONDecodeError conflated with OSError in pipeline verifier
   Files: forgelm/compliance.py, forgelm/cli/subcommands/_verify_annex_iv.py
   Verified: ``verify_pipeline_manifest_at_path`` emitted a shared
   "unreadable" sentinel for both ``OSError`` and ``JSONDecodeError``,
   so the CLI mapped malformed JSON to EXIT_TRAINING_ERROR (exit 2)
   even though parse failures are operator-actionable input errors
   (the single-artefact path maps them to exit 1).
   Fix: verifier now emits a distinct ``invalid JSON`` sentinel for
   ``JSONDecodeError`` and keeps ``unreadable`` for ``OSError`` only.
   CLI maps ``invalid JSON`` → EXIT_CONFIG_ERROR (1), ``unreadable``
   → EXIT_TRAINING_ERROR (2).  Existing test that asserted exit 2
   on malformed JSON was a stale carry-forward; updated to match
   the corrected contract.

🟡 MEDIUM 13 — Roadmap state inconsistent with the cut release
   Files: docs/roadmap.md, docs/roadmap-tr.md,
          docs/guides/pipeline.md, docs/guides/pipeline-tr.md
   Verified: Phase 14 row still said "📋 Planned" and the
   ``Current state:`` line still said "Phase 14 planned post-release"
   while CHANGELOG.md is dated [0.7.0] — 2026-05-14 and pyproject.toml
   reports version 0.7.0.  The pipeline guide's header said
   "currently on the Unreleased line of the CHANGELOG" which is no
   longer true.
   Fix: Phase 14 row → "🟡 Merged — implementation complete on
   `development`; `v0.7.0` PyPI tag is the release-pending step".
   Current-state and tree-comment lines updated to match.  Guide
   header → "landing in `v0.7.0` (CHANGELOG entry dated 2026-05-14;
   PyPI tag pending)".  TR mirrors updated in lockstep.  The full
   "Done" promotion to ``completed-phases.md`` lands as part of the
   post-tag dev-cycle reset commit (per cut-release skill step 4).

🟡 MEDIUM 14 — New pipelines.md pair not in bilingual parity registry
   File: tools/check_bilingual_parity.py
   Verified: docs/usermanuals/{en,tr}/training/pipelines.md added in
   the docs+site surface-refresh commit (d404092) but the
   heading-spine parity check (``_PAIRS`` registry) wasn't extended,
   leaving the pair open to structural drift.
   Fix: registered the pair in ``_PAIRS``.  The check now reports
   "48 bilingual doc pair(s)" (was 47).

⏭️ Skipped for v0.7.0 cut (documented for v0.7.x follow-up)
---
- HIGH 6 — canonical pipeline manifest hash + non-chain-field tamper
  detection: real but material design change (needs spec work +
  verifier overhaul on top of the existing Annex IV scheme).  Not a
  release blocker because (a) the structural verifier already catches
  the most common tamper shapes (chain integrity, stage indexing,
  status consistency), (b) the per-stage manifests retain their
  existing canonical hashes, and (c) the pipeline manifest is
  written atomically per transition and the surrounding audit log
  is hash-chained — a non-chain-field tamper window is narrow.
  Tracked for v0.7.x.

- HIGH 7 — disk verifier deep-parses each per-stage training_manifest:
  same complexity class as HIGH 6.  Currently the disk-wrapper checks
  ``os.path.isfile(per_stage_manifest)``; deep parsing would embed
  the full Annex IV single-artefact verifier per stage.  Filed as
  v0.7.x; pairs with HIGH 6 to land together as a "pipeline manifest
  verification rewrite" sub-phase.

- MEDIUM 10 — webhook ``pipeline.*`` events extend the 5-event
  vocabulary: events were intentionally added with a documented
  rollout (CHANGELOG enumerates 7 new ``pipeline.*`` events; the
  webhook contract's ``event`` field is an open-ended string, not
  an enum; receivers that hard-validate against a frozen enum need
  to be updated, but the schema explicitly leaves room).  Defer
  unless a downstream consumer reports the issue.

- MEDIUM 11 — ``_send(**extra)`` allowlist: ``**extra`` is
  caller-controlled (orchestrator methods that already control the
  call site), not from external input, and the base-field-collision
  guard prevents the obvious attack.  Tightening to an explicit
  allowlist is good hygiene but not a release blocker.  Tracked for
  v0.7.x.

Verification
---
- ruff format + check clean
- 222 / 222 tests pass (pipeline + compliance + CLI + dispatch +
  bilingual parity + anchor resolution + module size + cli
  subcommands)
- All 11 doc guards green in the gauntlet:
  dry-run, site-version --check, bilingual parity (48 pairs),
  anchor resolution (274 markdown files), CLI help consistency
  (457 invocations), wizard defaults, no-analysis-refs,
  no-unguarded-sys-modules-pop, build_usermanuals --check, ruff,
  module-size (--strict, 7 grandfathered)

Co-Authored-By: Claude Opus 4.7 (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.

Actionable comments posted: 4

🤖 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/configuration-tr.md`:
- Line 313: The docs incorrectly state pipeline.stages has a minimum cardinality
of 1; update the documentation for `pipeline.stages` / `List[PipelineStage]` to
indicate the Phase 14 contract requires 2+ stages (minimum 2) so users cannot
author configs that fail validation; make sure the text referencing `stages`,
`PipelineStage`, and the Phase 14 contract reflects "en az 2 zorunlu" (or
"minimum 2") and adjust any related parenthetical/default notes accordingly.

In `@docs/roadmap-tr.md`:
- Line 35: The document has conflicting release versions: one sentence states
PyPI shows v0.5.7 while line 35 claims v0.6.0 is published; update the
"PyPI'deki son sürüm" paragraph to match the v0.6.0 statement (and adjust its
date if needed) so the page consistently reports v0.6.0 as the latest release;
search for the literal strings "v0.5.7" and "PyPI'deki son sürüm" and
replace/update them to "v0.6.0" (and the correct release date) so both locations
and any related mentions like "v0.6.0-pro" remain consistent.

In `@forgelm/cli/_dispatch.py`:
- Line 277: Update the stale inline comment that currently says "removal is
slated for v0.7.0" to match the live warning text which was moved to v0.8.0;
locate the comment in _dispatch.py (near the alias/deprecation note around the
dispatch/alias handling) and change the version number from v0.7.0 to v0.8.0 so
the code comment and runtime message are consistent.

In `@tests/test_pipeline_cli.py`:
- Around line 120-140: Add a defensive assertion that at least one config was
captured before accessing configs_seen[0] in
test_empty_input_model_normalised_to_none: check that configs_seen is non-empty
(e.g. assert configs_seen or assert len(configs_seen) > 0) with a clear failure
message so the test fails clearly if the stage never executed, then proceed to
assert configs_seen[0].model.name_or_path as before.
🪄 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: 93b29e60-6175-4596-931e-605b174f75e4

📥 Commits

Reviewing files that changed from the base of the PR and between 277eebc and d3ed8cd.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • docs/guides/pipeline-tr.md
  • docs/guides/pipeline.md
  • docs/reference/configuration-tr.md
  • docs/reference/configuration.md
  • docs/reference/usage-tr.md
  • docs/reference/usage.md
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • forgelm/cli/_dispatch.py
  • forgelm/cli/_pipeline.py
  • forgelm/cli/subcommands/_verify_annex_iv.py
  • forgelm/compliance.py
  • tests/test_check_module_size.py
  • tests/test_pipeline_cli.py
  • tests/test_pipeline_compliance.py
  • tests/test_pipeline_orchestrator.py
  • tools/check_bilingual_parity.py
  • tools/check_module_size.py
✅ Files skipped from review due to trivial changes (2)
  • docs/reference/configuration.md
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • tools/check_module_size.py
  • tests/test_check_module_size.py
  • forgelm/cli/subcommands/_verify_annex_iv.py
  • tests/test_pipeline_compliance.py
  • forgelm/compliance.py
  • tests/test_pipeline_orchestrator.py

| Alan | Tip | Varsayılan | Açıklama |
|------|-----|-----------|----------|
| `output_dir` | string | `"./pipeline_run"` | Zincir seviyesi artefakların kök dizini: `pipeline_state.json`, `compliance/pipeline_manifest.json` ve pipeline-kapsamlı `audit_log.jsonl`. Aşama bazında trainer artefaktları her aşamanın kendi `training.output_dir`'ı altında kalır. |
| `stages` | `List[PipelineStage]` | `[]` (en az 1 zorunlu) | Sıralı aşama listesi. Her aşamanın `model.name_or_path`'ı, aşama explicit `model:` bloğu vermediği sürece, önceki aşamanın `training.output_dir/final_model`'ına otomatik ayarlanır. |

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

pipeline.stages minimum cardinality is documented incorrectly.

Line 313 says “en az 1 zorunlu”, but Phase 14 contract is 2+ stages. This should be updated to prevent users from authoring configs that fail validation.

🤖 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/reference/configuration-tr.md` at line 313, The docs incorrectly state
pipeline.stages has a minimum cardinality of 1; update the documentation for
`pipeline.stages` / `List[PipelineStage]` to indicate the Phase 14 contract
requires 2+ stages (minimum 2) so users cannot author configs that fail
validation; make sure the text referencing `stages`, `PipelineStage`, and the
Phase 14 contract reflects "en az 2 zorunlu" (or "minimum 2") and adjust any
related parenthetical/default notes accordingly.

Comment thread docs/roadmap-tr.md
Comment thread forgelm/cli/_dispatch.py Outdated
Comment thread tests/test_pipeline_cli.py
CI on PR #54 (run 25904206033) failed on the ``lint`` job:

  I001 [*] Import block is un-sorted or un-formatted
  --> tests/test_pipeline_cli.py:348:9

The ``test_pipeline_only_flags_rejected_on_non_pipeline_config`` test
added in d3ed8cd grouped ``import sys`` (stdlib) and ``import yaml``
(third-party) into a single block without the blank-line separator
ruff's isort layer wants between stdlib and third-party imports.

Local ``ruff check .`` had reported clean on commit time but the
post-format cache likely cleared between the format pass and the
check pass; CI's clean-cache invocation caught the violation.

Fix applied by ``ruff check --fix`` + ``ruff format``:
- Split the test-internal imports into stdlib / third-party / first-
  party groups separated by blank lines.
- ``ruff format`` minor whitespace normalisation in
  test_pipeline_orchestrator.py picked up at the same pass.

Verification:
- ruff check . → "All checks passed!"
- ruff format --check . → "208 files already formatted"
- 54 / 54 pipeline CLI + orchestrator tests still pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cemililik and others added 2 commits May 15, 2026 10:15
Two unrelated work items landed together because they were spotted in
the same review pass.

──────────────────────────────────────────────────────────────────
1. Nightly CI: requests-toolbelt missing on --no-deps install
──────────────────────────────────────────────────────────────────
Nightly compatibility workflow (.github/workflows/nightly.yml) ran
on ``main`` HEAD ccb7d84 (PR #51 merge) and failed:

  E   ModuleNotFoundError: No module named 'requests_toolbelt'

Across three jobs:
- Test with minimum supported versions (3.10)
- Test with latest dependencies (3.11)
- Test with latest dependencies (3.12)

Root cause: the v0.6.0 SSRF DNS-rebinding hardening (issue #14, PR
#52) added ``requests-toolbelt>=1.0.0,<2.0.0`` as a hard runtime
dependency in pyproject.toml.  Both failing nightly jobs install
ForgeLM via ``pip install -e . --no-deps`` (after a manual pip
install of the dependency list) — so the manual list MUST mirror
the pyproject dependency declarations, but ``requests-toolbelt``
was never added.  The 2026-05-15 nightly is the first run with this
test surface on main, hence the first failure.

Fix:
- ``test-latest-deps`` matrix: add ``requests-toolbelt`` to the
  unconstrained latest-deps pin line.  Added a comment block that
  spells out the contract (this list MUST mirror pyproject because
  the next step is --no-deps) so a future dependency addition
  doesn't recreate this gap.
- ``test-min-deps``: pin ``requests-toolbelt==1.0.0`` (the lower
  bound from pyproject's ``>=1.0.0,<2.0.0`` range) and added the
  same contract comment.
- The fix lands on development and propagates to main via PR #54.

──────────────────────────────────────────────────────────────────
2. PR #54 review findings — 3 valid (fix) + 1 skipped (reasoned)
──────────────────────────────────────────────────────────────────
SKIPPED — docs/reference/configuration-tr.md L313 "en az 1 zorunlu"
   Reviewer wanted "minimum 2 stages" but the schema enforces
   ``min_length=1`` (verified in ``forgelm/config.py:1351``).  The
   v0.7.0 cut CHANGELOG explicitly unified the wording around "at
   least 1 stage" with the note that single-stage ergonomics are
   better served by omitting the ``pipeline:`` block, but the
   schema accepts a 1-element pipeline so an operator can iterate
   up to a multi-stage chain without re-shaping the YAML.  Reviewer
   appears to be reading a prior unmerged variant; current code +
   docs are internally consistent.

FIXED — docs/roadmap{,-tr}.md "Latest release on PyPI" / "PyPI'deki
son sürüm" still said v0.5.7
   v0.6.0 shipped 2026-05-11; both EN and TR roadmaps still pointed
   "Latest release on PyPI" at v0.5.7, contradicting the same
   page's ``Current state: ... v0.6.0`` line just below.  Rewrote
   both paragraphs to lead with v0.6.0 (Phase 15 Ingestion Pipeline
   Reliability, 2026-05-11) and the full ingest-time-guard
   enumeration, with a roadmap link to ``completed-phases.md``.

FIXED — forgelm/cli/_dispatch.py L277 stale "v0.7.0" comment
   The dispatch comment for the ``--data-audit`` deprecation said
   "slated for removal in v0.7.0" but the matching DeprecationWarning
   message + audit-log ``version`` field were already updated to
   v0.8.0 in the v0.7.0 cut cycle (per the one-minor warning window
   policy).  Updated the inline comment to match, with a reference
   to ``docs/standards/release.md#deprecation-cadence``.

FIXED — tests/test_pipeline_cli.py:140 IndexError defensive guard
   (Sonar python:S1244 / S6395 Critical)
   ``test_empty_input_model_normalised_to_none`` accessed
   ``configs_seen[0]`` without first asserting the list was
   non-empty.  If a future dispatch-order change short-circuits the
   run before the fake trainer is instantiated, the test would
   fail with an opaque ``IndexError: list index out of range``
   instead of a "no stage ran" message.  Added an ``assert
   configs_seen, "..."`` guard with a descriptive failure message
   before the index access.

Verification:
- ruff format + check clean
- 196 / 196 targeted tests pass
- All 11 self-review guards green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codacy / bandit B112 flagged the collision-check pre-flight's silent
``except Exception: continue`` at forgelm/cli/_pipeline.py:731.

The skip is intentional — merge failures are re-surfaced via the
per-stage pipeline error path with full diagnostics, and the
pre-flight's contract is scoped to ``output_dir`` layout only.  But
the bare ``continue`` was indistinguishable from a hidden bug to a
static analyser.  Replaced with a ``logger.debug(...)`` that logs the
exception type + message before continuing, so the skip is *traceable*
on a verbose log without changing the pre-flight's behaviour.

Verification:
- ruff format --check + ruff check clean
- 54 / 54 pipeline + CLI tests pass

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

Copy link
Copy Markdown

@cemililik
cemililik merged commit cbffe62 into main May 15, 2026
11 checks passed
cemililik added a commit that referenced this pull request May 15, 2026
v0.7.0 published to PyPI (publish.yml run 25906894834, all 12 cross-OS
matrix cells green, OIDC trusted publish success).  Smoke test:
``pip install forgelm==0.7.0`` in a fresh venv reports
``forgelm --version → ForgeLM 0.7.0``.  Post-release housekeeping:

1. ``pyproject.toml`` → ``0.7.1rc1`` (next dev cycle marker)
2. ``docs/roadmap.md`` + ``-tr.md``:
   - Phase 14 row: "🟡 Merged" → "✅ Done" with v0.7.0 release
     date + completed-phases anchor
   - "Latest release on PyPI" / "PyPI'deki son sürüm" paragraph:
     v0.6.0 → v0.7.0 with the Phase 14 + SSRF hardening summary;
     v0.6.0 demoted to "Previous release" / "Önceki sürüm"
   - "Released:" / "Yayınlandı:" line: v0.6.0 → v0.7.0
   - "Current state" line: 20 phases → 21 phases (Phase 14 added),
     PyPI through v0.7.0
   - Mermaid: ``v0.7.0`` node → ``v0.7.0 ✅ Released`` /
     ``✅ Yayınlandı``
   - Tree-comment for ``phase-14-pipeline-chains.md``: "Merged …
     v0.7.0 tag pending" → "Done — shipped v0.7.0 (PyPI 2026-05-15)"
   - ``releases.md`` comment: ``v0.3.0 → v0.6.0`` → ``v0.3.0 →
     v0.7.0``
3. ``docs/roadmap/completed-phases.md``: appended a comprehensive
   Phase 14 entry (driver, what shipped, security fold-in,
   backward-compat invariant, full review-absorption history with
   PR #53 + #54 round summaries, cross-references to operator
   guides + schema + user manual pages).  Slug
   ``#phase-14-multi-stage-pipeline-chains-v070`` matches the
   target anchor used from ``roadmap.md`` and ``roadmap-tr.md``.

Verification:
- ruff format --check + ruff check clean
- 48 / 48 bilingual doc pair parity green
- 274 markdown files: all anchors + relative links resolve
  (including the new ``completed-phases.md#phase-14-...`` anchors)
- site/ version literals still pin v0.7.0 (no change needed; the
  bump to 0.7.1rc1 is dev-cycle only)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cemililik added a commit that referenced this pull request May 15, 2026
…ing)

v0.7.0 shipped Phase 14 (Multi-Stage Pipeline Chains) on 2026-05-15.
Post-release housekeeping:

1. Phase 14.5 — Pipeline Hardening (new planning doc)
   ``docs/roadmap/phase-14-5-pipeline-hardening.md`` is the canonical
   home for the 4 review-deferred items from PR #54:
   - F-PR54-H6: canonical pipeline manifest hash + non-chain-field
     tamper detection
   - F-PR54-H7: per-stage ``training_manifest.json`` deep-parse
     validation (pairs with H6 as "pipeline manifest verification
     rewrite")
   - F-PR54-M10: webhook ``pipeline.*`` event vocabulary
     documentation (optional ``tools/check_webhook_event_vocabulary
     .py`` ``--strict`` guard)
   - F-PR54-M11: ``WebhookNotifier._send(**extra)`` explicit
     allowlist
   Wave 2 carry-overs from the original Phase 14 spec (intra-stage
   HF Trainer resume, DAG pipelines, parallel exec, wizard pipeline
   path) tracked at the bottom as future-phase backlog, NOT in-flight
   Phase 14.5 work.  Targets v0.7.x patch cycle.

2. ``docs/roadmap/phase-14-pipeline-chains.md`` deleted
   Phase 14 shipped in v0.7.0; the canonical history is in
   ``completed-phases.md#phase-14-multi-stage-pipeline-chains-v070``
   (the entry I appended in commit bc4c4a0).  Phase 15 followed the
   same pattern (3399c0e archived its standalone planning doc once
   shipped).  Six cross-references redirected to either the
   completed-phases anchor or the new Phase 14.5 doc:
   - ``docs/guides/pipeline{,-tr}.md`` Cross-references section
   - ``docs/usermanuals/{en,tr}/deployment/model-merging.md``
     merge-sweep helper aside
   - ``docs/roadmap/completed-phases.md`` historical reslotting note
     (L915) + cross-references list (L1535)

3. ``docs/roadmap.md`` + ``docs/roadmap-tr.md``
   - New Phase 14.5 row in the Planned section (📋 Planned / Planlandı).
   - Mermaid diagram: new ``P145`` node + ``V275[v0.7.x]`` dotted edge.
   - Tree-structure comment: ``phase-14-pipeline-chains.md`` →
     ``phase-14-5-pipeline-hardening.md`` (the design doc no longer
     exists; the new planning doc takes its slot).

4. ``docs/roadmap/releases.md``
   - "v0.7.0 — Pipeline Chains (Planned)" rewritten as a Released
     entry with full Summary / Highlights / Public surface changes /
     Review-absorption history / Full changelog sections (mirrors
     the v0.6.0 entry's depth).
   - New "v0.7.x — Pipeline Hardening (Planned)" placeholder
     between v0.7.0 and v0.6.0-pro pointing at the Phase 14.5 spec.

5. ``docs/roadmap/risks-and-decisions.md``
   - New "2026-05-15 — v0.7.0 release-review deferrals → Phase 14.5"
     deferred-findings table with the 4 F-PR54 rows + bundled-landing
     note.
   - Three new decision-log entries: 2026-05-11 (v0.6.0 / Phase 15),
     2026-05-15 (v0.7.0 / Phase 14 + retag), 2026-05-15 (Phase 14.5
     introduction).

Verification:
- bilingual parity: 48/48 pairs (the new Phase 14.5 doc is EN-only;
  TR mirror not added since this is a planning doc, not user-facing
  reference — same pattern as phase-13-pro-cli.md)
- anchor resolution: 274 markdown files green (every Phase 14.5
  cross-reference resolves, the completed-phases anchor links resolve)
- CLI help consistency: 457 forgelm invocations green
- no-analysis-refs: green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant