Skip to content

retention.enforce (block_on_excess / warn_on_excess) has no runtime reader: the documented trainer pre-flight gate never runs (CR-A01-006) #92

Open

Description

@cemililik

Wave 0 · theme D0-5: Documented controls that are not implemented · highest severity Critical · 10 finding(s) · effort medium

Summary

A regulated CI sets retention.enforce: block_on_excess and reuses a workspace whose audit_log.jsonl is past audit_log_retention_days. Training runs and exits 0, nothing is logged and no exit 3 occurs. The operator believes storage limitation is being enforced.

Primary finding

CR-A01-006 — retention.enforce (block_on_excess / warn_on_excess) has no runtime reader: the documented trainer pre-flight gate never runs

severity High · reachability documented-opt-in · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end

What happens. RetentionConfig.enforce (forgelm/config.py:1585-1592, docstring 1540-1546) is validated, offered by the wizard's _collect_retention as 'block_on_excess (abort trainer with EXIT_EVAL_FAILURE)' (forgelm/wizard/_collectors.py:863-877) and documented (configuration.md:266) as a trainer pre-flight abort with exit 3. Nothing in forgelm/ reads .enforce: the trainer and CLI train path never call _scan_retention_violations, and the only retention reader, purge --check-policy (forgelm/cli/subcommands/_purge.py:1108-1170), ignores enforce and always exits 0.

Where.

Evidence.

  • rg for '.enforce', 'block_on_excess' and 'warn_on_excess' in forgelm/ finds only the schema, the wizard prompt and an _audit.py docstring that cites the 'retention block_on_excess gate'. config.retention is read only at _purge.py:1141/1153, and that path always ends in sys.exit(EXIT_SUCCESS).
  • rg -n '\.enforce\b|"retention"|retention_days|staging_ttl' forgelm --glob '*.py' finds no reader outside config/wizard/purge. Repro r15: the output dir holds final_model.staging.fg-old000000000, whose first audit event is 200 days old, and purge --check-policy reports staging_dir (age 200d, horizon 7d) and compliance_bundle violations. fpy -m forgelm --config r15/cfg.yaml with retention.enforce: block_on_excess then exits 0 with success true and writes final_model/. The audit log gets n…
  • rg -n "\.enforce\b|\"enforce\"|'enforce'|retention\." forgelm matches only config.py and _purge.py horizon reads. The repro config ret/cfg.yaml sets retention.enforce=block_on_excess and ephemeral_artefact_retention_days=1, with out/data_audit_report.json mtime set to 2020. forgelm purge --check-policy --config cfg.yaml --output-dir out --output-format json reports count 1 (data_audit_report, age 2465.9d, horizon 1d). fpy -m forgelm --config cfg.yaml (tiny-qwen SFT, 1 step) then ends with…

Recommended fix. Implement the pre-flight check in the training path (reuse _scan_retention_violations; map block_on_excess to EXIT_EVAL_FAILURE and emit an audit event), or remove the promise from the field, the docstring, the wizard and the docs and raise NotImplementedError when a non-default mode is set.

Other findings with the same root cause

These close with the same fix; update them in the same pull request.

  • CR-A02-021 (code, High) — Code does not match docs/design/gdpr_erasure.md: §3.4 site 1: ForgeTrainer.train() runs a retention pre-flight that honours enforce. L143: 'When set, the trainer's pre-flight gate runs th
    severity High · reachability documented-opt-in · intent contract-violation · confidence confirmed · independently confirmed
    Failure scenario. The document states: §3.4 site 1: ForgeTrainer.train() runs a retention pre-flight that honours enforce. L143: 'When set, the trainer's pre-flight gate runs the check'. L136-140 define the enforce semantics; L211 reserves exit 3 for block_on_excess; L447 names it 'the CI gate'.. Observed in the code: No retention pre-flight exists in the trainer or anywhere else, and retention.enforce is never read. The design is also internally inconsistent: L139 says block_on_excess raises ConfigError, wh…
    Where. forgelm/trainer.py:1571-1580 (_run_training_pipeline start; the cited 1569-1660 range is OOM/pipeline helpers, not a retention site), forgelm/config.py:1585-1591, forgelm/cli/subcommands/_purge.py:1108-1168 (_run_purge_check_policy, the only consumer of config.retention; always exits 0), forgelm/trainer.py:1661-1700 (train(): no retention pre-flight)
    Recommended fix. Fix the code (implement the pre-flight) or mark §3.4 site 1 and the enforce semantics as "NOT IMPLEMENTED — enforce is currently inert", and pick one exit behaviour (exit 3) for block_on_excess.

  • CR-A10-006 (code, High) — Retention policy is report-only: no code enforces retention.enforce or deletes/prunes artefacts past their horizon, though docs, config and site say it does
    severity High · reachability documented-opt-in · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end
    Failure scenario. The document states: Retention horizons, including "staging_ttl_days: 7 # final_model.staging.<run_id>/ TTL after forgelm reject". Observed in the code: Nothing deletes or expires artefacts when a horizon passes. staging_ttl_days and the other horizons are read only by forgelm purge --check-policy, which lists violations and exits 0. No scheduled cleanup exists, and approve/reject never read the TTL.
    Where. forgelm/cli/subcommands/_purge.py:1171-1237 (_scan_retention_violations, report-only), forgelm/config.py:1526-1592 (RetentionConfig; docstring 1540-1546, staging_ttl_days 1559-1566, enforce 1585-1592), forgelm/cli/subcommands/_purge.py:1108-1168 (_run_purge_check_policy; exits 0 at 1150 and 1168), forgelm/cli/subcommands/_purge.py:1171-1236 (confirmed)
    Recommended fix. Add: "# Horizons are reported by forgelm purge --check-policy (read-only); nothing is deleted automatically — remove artefacts with forgelm purge."

  • DOC-D1B01-003 (docs, Critical) — docs/reference/configuration.md: false claim — "The enforce knob switches between log-only, warning, and hard-block modes so a regulated CI gate cannot silently extend the retention horizon". Also…
    severity Critical
    Where. docs/reference/configuration.md:256-258 (Turkish mirror: docs/reference/configuration-tr.md:252-258, :264, :267) · type: false claim
    The document says. "The enforce knob switches between log-only, warning, and hard-block modes so a regulated CI gate cannot silently extend the retention horizon". Also :266 block_on_excess "(abort trainer pre-flight with EXIT_EVAL_FAILURE = 3)", log_only "(audit-log only)", and :263 staging_ttl_days "before…
    What the code does. Nothing reads retention.enforce at runtime. A grep finds it only in the schema (config.py:1526-1600) and the wizard prompt (wizard/_collectors.py:877). The trainer has no retention pre-flight, writes no audit event and prints no warning. No scheduled cleanup exists. The only consumer of the retention block is forgelm purge --check-policy (_purge.py:1141-1170), a read-only report that always exi…
    Code references. forgelm/config.py:1526-1600, forgelm/wizard/_collectors.py:877, forgelm/cli/subcommands/_purge.py:1141-1170, forgelm/cli/subcommands/_purge.py:1171-1226
    Proposed correction. Fix the code: implement the trainer pre-flight. Run _scan_retention_violations on training.output_dir; log_only emits an audit event, warn_on_excess prints a structured stderr warning, block_on_excess exits 3. Temporary doc warning: "enforce is currently recorded but not enforced: no mode aborts training, warns or writes an audit event, and noth…
    Turkish mirror. the Turkish page has no such statement

  • DOC-D2B02-001 (docs, Critical) — docs/design/gdpr_erasure.md: false claim — §3.4 site 1: ForgeTrainer.train() runs a retention pre-flight that honours enforce. L143: 'When set, the trainer's pre-flight gate runs the check'. L136-1…
    severity Critical
    Where. docs/design/gdpr_erasure.md:156-158 · type: false claim
    The document says. §3.4 site 1: ForgeTrainer.train() runs a retention pre-flight that honours enforce. L143: 'When set, the trainer's pre-flight gate runs the check'. L136-140 define the enforce semantics; L211 reserves exit 3 for block_on_excess; L447 names it 'the CI gate'.
    What the code does. No retention pre-flight exists in the trainer or anywhere else, and retention.enforce is never read. The design is also internally inconsistent: L139 says block_on_excess raises ConfigError, while L211 and the config description say exit 3.
    Code references. forgelm/trainer.py:1569-1660, forgelm/config.py:1585-1591, forgelm/cli/subcommands/_purge.py:1108-1168
    Proposed correction. Fix the code (implement the pre-flight) or mark §3.4 site 1 and the enforce semantics as "NOT IMPLEMENTED — enforce is currently inert", and pick one exit behaviour (exit 3) for block_on_excess.

  • DOC-D2B02-002 (docs, Critical) — docs/guides/gdpr_erasure.md: false claim — enforce controls how the trainer pre-flight gate reacts to retention violations (log_only / warn_on_excess / block_on_excess, L81).
    severity Critical
    Where. docs/guides/gdpr_erasure.md:84 (Turkish mirror: docs/guides/gdpr_erasure-tr.md:84) · type: false claim
    The document says. enforce controls how the trainer pre-flight gate reacts to retention violations (log_only / warn_on_excess / block_on_excess, L81).
    What the code does. No trainer pre-flight retention gate exists. retention.enforce is never read anywhere outside the schema. A search for retention across forgelm/ finds only config.py (the field and its description, which itself promises 'block_on_excess aborts the trainer pre-flight with EXIT_EVAL_FAILURE (3)'), the purge --check-policy report (which ignores enforce), and the wizard prompt. A training run wit…
    Code references. forgelm/config.py:1585-1591, forgelm/cli/subcommands/_purge.py:1108-1168, forgelm/trainer.py:1569-1660, forgelm/wizard/_collectors.py:866-868
    Proposed correction. Fix the code: implement the documented pre-flight in ForgeTrainer.train(), or remove enforce from RetentionConfig. Until then, replace the sentence with: "Note: enforce is currently not acted on by any command. Training does not check retention. Run forgelm purge --check-policy yourself and gate CI on its JSON count."
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-BFIELD-003 (docs, High) — forgelm/config.py: false claim — log_only records violations in the audit log; warn_on_excess adds a structured warning to stderr; block_on_excess aborts the trainer pre-flight with EXIT_EVAL_F…
    severity High
    Where. forgelm/config.py:1585-1592 (RetentionConfig.enforce, F68) (Turkish mirror: docs/reference/configuration.md:266; config_template.yaml:274; docs/usermanuals/en/reference/configuration.md:297) · type: false claim
    The document says. log_only records violations in the audit log; warn_on_excess adds a structured warning to stderr; block_on_excess aborts the trainer pre-flight with EXIT_EVAL_FAILURE (3)
    What the code does. retention.enforce is never read: rg '\.enforce\b' forgelm finds only config.py and the wizard collector (wizard/_collectors.py:863-877). The trainer has no retention pre-flight; there is no 'retention' match in trainer.py, cli/_training.py or cli/_pipeline.py. The only consumer of the retention block is forgelm purge --check-policy (cli/subcommands/_purge.py:1108-1169). It prints a report, al…
    Code references. forgelm/cli/subcommands/_purge.py:1108-1169, forgelm/cli/subcommands/_purge.py:1171-1236, forgelm/wizard/_collectors.py:863-877
    Proposed correction. Implement the trainer pre-flight (call _scan_retention_violations against training.output_dir, emit an audit event, warn or exit 3 according to enforce), or rewrite the description and docs to say that retention horizons are report-only through forgelm purge --check-policy and that enforce is currently ignored.

  • DOC-D4B02-004 (docs, High) — config_template.yaml: false claim — enforce: "log_only" # "log_only" | "warn_on_excess" | "block_on_excess" — presented as a working retention enforcement mode
    severity High
    Where. config_template.yaml:274 · type: false claim
    The document says. enforce: "log_only" # "log_only" | "warn_on_excess" | "block_on_excess" — presented as a working retention enforcement mode
    What the code does. RetentionConfig.enforce is validated but read by no code: rg over forgelm/ finds only the wizard writing it. The trainer has no retention pre-flight, so block_on_excess never aborts and warn_on_excess never warns. The only consumer of the retention block is forgelm purge --check-policy, a read-only report that always exits 0.
    Code references. forgelm/config.py:1585-1592, forgelm/cli/subcommands/_purge.py:1125-1172, forgelm/cli/subcommands/_purge.py:1175-1234, forgelm/wizard/_collectors.py:863-877
    Proposed correction. Fix the code (implement the retention pre-flight). Temporary doc note: "# NOTE: enforce is currently recorded only — no mode warns or blocks; run forgelm purge --check-policy to list violations."

  • DOC-D4B09-012 (docs, High) — site/js/translations.js: false claim — With retention.enforce set, 'ForgeLM auto-deletes artefacts past their horizon'; log_only 'logs violations to audit'; warn_on_excess 'log + stderr warning'; blo…
    severity High
    Where. site/js/translations.js:1820-1823 (wizard.retention.enforce.hint/.log_only/.warn/.block), :1812 · type: false claim
    The document says. With retention.enforce set, 'ForgeLM auto-deletes artefacts past their horizon'; log_only 'logs violations to audit'; warn_on_excess 'log + stderr warning'; block_on_excess 'abort trainer (regulated CI)'
    What the code does. RetentionConfig.enforce (forgelm/config.py:1585) is declared but nothing reads it: no consumer exists in trainer, cli or compliance, and wizard/_collectors.py only writes it. The horizons are consumed only by 'forgelm purge --check-policy', which prints a report and exits 0 (cli/subcommands/_purge.py:1141-1175). No code deletes artefacts, writes a retention violation to the audit log, or aborts t…
    Code references. forgelm/config.py:1526-1593, forgelm/cli/subcommands/_purge.py:1141-1175
    Proposed correction. Replace the hint with 'Retention horizons are report-only today: run forgelm purge --check-policy to list artefacts past their horizon. The enforce mode is not yet wired to the trainer; nothing is deleted automatically.' Remove the three mode descriptions, or mark them 'not yet enforced'.

  • DOC-D4B02-011 (docs, Medium) — config_template.yaml: misleading — Retention horizons, including "staging_ttl_days: 7 # final_model.staging.<run_id>/ TTL after forgelm reject"
    severity Medium
    Where. config_template.yaml:266-273 · type: misleading
    The document says. Retention horizons, including "staging_ttl_days: 7 # final_model.staging.<run_id>/ TTL after forgelm reject"
    What the code does. Nothing deletes or expires artefacts when a horizon passes. staging_ttl_days and the other horizons are read only by forgelm purge --check-policy, which lists violations and exits 0. No scheduled cleanup exists, and approve/reject never read the TTL.
    Code references. forgelm/cli/subcommands/_purge.py:1175-1234, forgelm/config.py:1559-1567
    Proposed correction. Add: "# Horizons are reported by forgelm purge --check-policy (read-only); nothing is deleted automatically — remove artefacts with forgelm purge."

Acceptance criteria

  • A regression test reproduces the failure scenario and asserts the corrected behaviour.
  • Documentation listed above is corrected in English and in its Turkish mirror in the same PR.
  • Every finding in this issue is resolved or explicitly closed with a reason.
  • The release note names the contract this fix restores.

Found by the October 2026 ForgeLM code and documentation review against commit cab2947. Code links point at that commit; line numbers may have moved since. Finding IDs (CR-…, DOC-…, TECH-…) are stable references for this backlog.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: configCode area: configarea: gdpr-approvalCode area: gdpr-approvalarea: trainerCode area: trainerbugSomething isn't workingdocumentationImprovements or additions to documentationseverity: criticalBreaks a stated security or safety contractsource: review-2026-10Found by the October 2026 code and documentation reviewwave: 0Roadmap wave 0: immediate (patch release)

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions