Repository navigation
[Wave 4] Low/info — Module sweep: Compliance core: Annex IV, audit log, integrity manifest, provenance (part 3/5) #658
Description
Wave 4 · 20 finding(s) in 20 fix package(s). Fix these whenever a pull request touches this module ("clean the module you touch").
Findings
-
CR-A07-109 (code, Low) — verify-annex-iv --pipeline exits 0 for a chain manifest whose manifest_hash was stripped, without version gating
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end · already tracked: KNOWN-OPEN TRUST-05 — docs/roadmap/phase-16-trust-surface-hardening.md:440-442 (S5 open)
Failure scenario. A clean 2-stage run with forgelm_version 1.0.0 and an HMAC log; delete metadata from compliance/pipeline_manifest.json -> exit 0, success true, hash_state 'absent'.
Where.forgelm/compliance.py:2083-2090,forgelm/cli/subcommands/_verify_annex_iv.py:227-236,forgelm/verify/_pipeline_evidence.py:452-462
Recommended fix. Route an absent chain manifest_hash to UNVERIFIED:: (exit 1) when forgelm_version is at or after the release that began stamping it, mirroring _manifest_predates_pointer_fix. Alternatively, document the exit-0 'OK (UNVERIFIED)' case in cli.md:227. -
CR-A07-110 (code, Low) — AuditLogger does not catch socket.gethostname() OSError
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. getpass resolves 'alice', socket.gethostname raises OSError -> AuditLogger raises OSError (uncaught).
Where.forgelm/compliance.py:215,forgelm/cli/subcommands/_approve.py:174-181,forgelm/trainer.py:354,forgelm/cli/subcommands/_purge.py:736-739
Recommended fix. Wrap gethostname in try/except OSError, falling back to 'unknown-host' as _approve.py does. -
CR-A07-111 (code, Low) — Docs and docstring claim an all-local-path config yields component_revisions: [], but local-path loads are recorded as local_path entries
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An auditor follows the documented reading 'an all-local-path config gives []'. A non-empty list of local_path entries then looks like evidence of Hub-pinned loads, or the auditor wrongly concludes that the manifest is malformed.
Where.forgelm/compliance.py:1466-1468,forgelm/model.py:72-76,forgelm/model.py:185-189,forgelm/compliance.py:1296-1298
Recommended fix. Fix the three doc sites and the model.py:72-75 comment: local-path loads appear with resolution_source local_path and revision_pinned null. Or, if the documented behaviour is intended, filter local_path records out. Pin it with tests/test_revision_pin_chains.py::test_local_path_load_is_listed_as_local_path. -
CR-A07-112 (code, Low) — --compliance-export grades a local-directory base model 'unresolved' + 'no base-model load' reason; a training run on the same config grades it 'local_path'
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An auditor compares the bundle fromforgelm --config job.yaml --compliance-export ./pre(written before training) with the run's own compliance/ directory. For an identical local base model the two artefacts disagree ('unresolved' vs 'local_path'), and the pre-training one claims nothing could be determined.
Where.forgelm/compliance.py:1374-1421,forgelm/compliance.py:1296-1298,forgelm/compliance.py:1105-1107,docs/reference/compliance_summary.md:159-160
Recommended fix. When the registry has no record and os.path.isdir(repo_id), emit resolution_source 'local_path' (the reason may still say no load happened), mirroring the dataset side; or document explicitly that --compliance-export never emits local_path for the base model. -
CR-A07-113 (code, Low) — compliance_summary says export_compliance_artifacts 'ZIPs the bundle'; no CLI path produces a ZIP and export_evidence_bundle has no caller
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An auditor or operator expects an auditor-ready ZIP after --compliance-export, as the reference says, and finds loose files and no bundle.
Where.docs/reference/compliance_summary.md:26-27,docs/reference/compliance_summary.md:89-91,docs/reference/compliance_summary-tr.md:25-26,docs/reference/compliance_summary-tr.md:88-90
Recommended fix. Either wire export_evidence_bundle into --compliance-export (or a flag), or reword both sites (EN+TR) to say the export writes loose files and the ZIP helper is library-only. -
CR-A07-114 (code, Low) — Governance split statistics have an undisclosed basis that depends on the trainer: text_length disappears silently for DPO/SimPO/ORPO/KTO/GRPO and measures the [ROLE]-formatted string plus EOS for SF…
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An auditor compares the text_length of a DPO run with that of an SFT run. The DPO report has no length data and no reason for its absence. The SFT numbers include role markers and the EOS token, and they disagree with the inlined data_audit splits for the same rows: 24 vs 14 characters in e2e. sample_count can be read as raw corpus rows or as rows after truncation or dedup, and the report supports neither reading.
Where.forgelm/compliance.py:617-651,forgelm/compliance.py:643-651,forgelm/data.py:634-645,forgelm/data.py:648-666
Recommended fix. Add a 'basis' block to each split entry (stage: post-merge/post-split/pre-tokenisation; length unit: characters of the rendered training string; truncation: model.max_length tokens not applied). For non-text trainers, compute lengths per prompt/chosen/rejected/completion column or record text_length: null with a reason. Document the schema in the compliance reference and the user manual. -
CR-A07-115 (code, Low) — Governance 'median' and 'p95' are upper-biased order statistics: the median of an even-sized split is its upper middle value (n=2 gives the max), and p95 at n=20 is the max
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. A split with lengths [1,4] reports median 4 (true median 2.5). A 20-row split [0..19] reports p95 19 (nearest-rank 18). In e2e, validation (4 rows) reports median 27 = max.
Where.forgelm/compliance.py:638-639
Recommended fix. Use statistics.median and a documented percentile definition (nearest-rank ceil(0.95*n)-1), or rename the keys to the order statistic actually returned. -
CR-A07-116 (code, Low) — No test asserts the governance split statistics: the mutants sample_count+1 and median->lengths[0] survive the full suite
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. Private copy with compliance.py:645 set to len(split_data)+1 and compliance.py:638 set to lengths[0]: the full suite reports 4746 passed, 24 skipped, 0 failed. The unmutated baseline copy reports 3 failed, all of them known offline-environment provenance tests unrelated to this code.
Where.forgelm/compliance.py:645,tests/test_compliance.py:689-724,tests/test_faz27_narrow_exceptions.py:303-354
Recommended fix. Add a test that runs prepare_dataset on a small primary + extra fixture through generate_data_governance_report and asserts the per-split counts, the source attribution and the length statistics (including an even-n median). -
CR-A07-117 (code, Low) — Annex IV §2 / compliance_report training_parameters omit every method-specific objective parameter and most optimisation settings for all six trainer types; only config_hash and the model card (outsi…
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · independently confirmed · reproduced end to end
Failure scenario. A regulator receives the compliance/ bundle for a DPO model (SOC2 CC2.3 names the Annex IV bundle + compliance_report.json as what is shipped). It cannot tell beta 0.1 from 0.5, whether NEFTune or GaLore was active, the warmup/weight decay, or the step budget, so the documented claim that §2 lets the auditor 'reconstruct what was run' does not hold without obtaining the original YAML or model card separately.
Where.forgelm/compliance.py:1516,forgelm/compliance.py:1836-1842,forgelm/trainer.py:496-552,forgelm/trainer.py:693-765
Recommended fix. Add a per-trainer_type block (betas, gamma, GRPO generation parameters, reward source), the optimisation settings (max_steps, warmup, weight decay, NEFTune, GaLore, packing, early stopping, scheduler, seed, optim/precision) and the TRL/transformers versions to training_parameters, or embed the redacted validated config with its config_hash in the compliance bundle. -
CR-A07-118 (code, Low) — The declaration of conformity is attributed to the 'deployer' under 'Article 16', and the introduction claims 'conformity declaration scaffolding' that does not exist
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · single reviewer · not reproduced (code trace)
Failure scenario. A team that fine-tunes and ships a high-risk system reads that the DoC is 'the deployer's' deliverable and assumes its downstream customer signs it. The provider-side Annex IV §8 copy and the Art. 47 declaration are never produced. A new reader, told by the introduction that scaffolding exists, looks for a DoC template that is not there.
Where.docs/usermanuals/en/compliance/annex-iv.md:183,185,docs/usermanuals/tr/compliance/annex-iv.md:183,docs/usermanuals/en/compliance/overview.md:61,docs/usermanuals/tr/compliance/overview.md:61
Recommended fix. Say 'the provider's (Art. 16(g), Art. 47) signed deliverable, and a copy belongs in Annex IV §8'. Remove 'conformity declaration scaffolding' from introduction.md (EN+TR), or ship an actual template. -
CR-A07-119 (code, Low) — Art. 13 deployer instructions omit the provider's contact details (Art. 13(3)(a)) even when compliance.provider_contact is configured, while telling deployers to 'contact the provider'
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · single reviewer · reproduced end to end
Failure scenario. A deployer receives deployer_instructions.md for a high-risk model. The model produces harmful output and Section 7 says to report it to the provider immediately, but the document has no contact. The Art. 13(3)(a) element is missing even though the operator supplied it.
Where.forgelm/compliance.py:1686-1694,forgelm/compliance.py:1704-1712,forgelm/compliance.py:1745,forgelm/compliance.py:1758
Recommended fix. Add a 'Provider Contact' row from comp_cfg.provider_contact through _sanitize_md, plus an optional authorised-representative field. Add a test asserting that the contact appears. -
CR-A07-120 (code, Low) — verify_annex_iv_subcommand.md examples use a filename no writer produces (annex_iv.json) and a 'sha256:' hash prefix the verifier never emits
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · single reviewer · reproduced end to end
Failure scenario. An integrator copies the reference example into a CI gate that checks the path compliance/annex_iv.json or matches the hash with a 'sha256:' prefix. The path never exists (exit 1), or the prefix match never succeeds.
Where.docs/reference/verify_annex_iv_subcommand.md:182-229,docs/reference/verify_annex_iv_subcommand-tr.md:182-229,forgelm/compliance.py:87
Recommended fix. Change the examples to compliance/annex_iv_metadata.json and bare-hex hashes in both EN and TR. -
CR-A07-121 (code, Low) — Duplicated manifest entries inflate verified_count: 'All 4 recorded artifact(s) present and unchanged' for a two-file directory
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. A padded manifest makes a release record claim twice as many verified artifacts as exist.
Where.forgelm/verify/_model_integrity.py:152-195,forgelm/verify/_model_integrity.py:225-229
Recommended fix. Reject duplicatefilevalues (after canonicalisation) as a malformed manifest (exit 1), or count distinct paths. -
CR-A07-122 (code, Low) — corroborate_pipeline_stage_census: _live_passed_stages is O(n^2) in the number of stages (set comprehension on every stage_started event)
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. A pipeline (or a key-holding producer) with tens of thousands of stages makes verify-annex-iv --pipeline take minutes in the corroboration step alone, growing quadratically up to the 32 MiB log cap (about 2 minutes at 40k stages, much longer if a smaller per-event size or a faster path increased the event count).
Where.forgelm/compliance.py:3409,forgelm/compliance.py:3294-3362,forgelm/verify/_pipeline_evidence.py:600-605,forgelm/compliance.py:3353-3357
Recommended fix. Make supersession amortised O(log n): keep live as a dict {name: position} plus a lazy max-heap of (-position, name); on stage_started pop heap entries with position >= cutoff and delete them from live if the dict still holds that same position. A scratch implementation (bench/fixlive.py) was equal to the original on 3000 random event traces (including unknown names and repeated starts) and ran N… -
CR-A07-123 (code, Low) — verify-annex-iv --pipeline interpolates the first 16 characters of the manifest's own manifest_hash unescaped into a violation line
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · not reproduced (code trace)
Failure scenario. A pipeline manifest whose metadata.manifest_hash string contains control characters fails verification with exit 6. Up to 16 of its characters reach the terminal unescaped inside the 'manifest hash mismatch' violation line.
Where.forgelm/compliance.py:2082-2090,forgelm/cli/subcommands/_verify_annex_iv.py:238-240
Recommended fix. Use {expected_hash!r:.20} or str(expected_hash)[:16] passed through the same control-character escaping used elsewhere, and type-check expected_hash as str before slicing. -
CR-A07-124 (code, Low) — library_api_reference.md does not document the pipeline verifier facade or its INPUT_ERROR::/UNVERIFIED::/IO_ERROR:: routing tokens. Library callers have no documented way to tell exits 1, 2 and 6 ap…
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An integrator calls verify_pipeline_manifest_at_path from Airflow and treats any non-empty list as tampering. An environment without FORGELM_AUDIT_SECRET always returns an UNVERIFIED:: entry, so routine runs page the security on-call. Alternatively, the integrator matches prose and misroutes.
Where.docs/reference/library_api_reference.md:29,docs/reference/library_api_reference.md:89,forgelm/compliance.py:3529-3551,forgelm/compliance.py:3083-3096
Recommended fix. Document the facade in library_api_reference.md (EN+TR): the token prefixes, the precedence order, the env dependency on FORGELM_AUDIT_SECRET, and the need to strip tokens before display. Alternatively, expose a public is_pipeline_integrity_failure / classify helper. Fix the stale docstring at compliance.py:3090-3094. -
CR-A07-125 (code, Low) — verify-audit and the pipeline corroborator read the audit log without taking the writer's flock, so a live append can be reported as tampering (exit 6) or as an empty log (exit 1)
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced with stubs
Failure scenario. A monitoring job runs verify-audit against the log of a run in progress, or of a shared output directory, and gets a transient exit 6 that disappears on re-run. Responders learn to ignore exit 6.
Where.forgelm/compliance.py:2594-2695,forgelm/compliance.py:548-599,forgelm/cli/subcommands/_verify_audit.py:71-106,forgelm/compliance.py:3445
Recommended fix. Take fcntl.flock(LOCK_SH) with a bounded wait in _read_audit_log_lines; a no-op on Windows, as in the writer. Or at least treat an unterminated final line as 'incomplete/in progress' (not exit 6), and document that verify-audit should not be run against a log being written. -
CR-A07-126 (code, Low) — Enabling monitoring removes the audit-log reference from Annex IV §6 'monitoring_and_logging'
severity Low · reachabilitydefault· intentcontract-violation· confidence likely · single reviewer · reproduced end to end
Failure scenario. An operator enables monitoring to strengthen the Art. 12/17 posture. The resulting annex_iv_metadata.json §6 no longer names audit_log.jsonl, so an auditor reading §6 finds no record-keeping reference. The same run without monitoring would have had one.
Where.forgelm/compliance.py:1856-1857,forgelm/compliance.py:1582-1589,forgelm/verify/_annex_iv.py:41,docs/usermanuals/en/compliance/annex-iv.md:85
Recommended fix. Merge rather than replace: {'audit_log': 'audit_log.jsonl', **({'monitoring': manifest['monitoring']} if manifest.get('monitoring') else {})}. Add a test asserting audit_log stays present when monitoring is enabled. -
CR-A07-127 (code, Low) — Dataset URLs with credentials are copied verbatim into compliance_report.json / data_provenance.json (path, fingerprint.path) by --compliance-export, and into error envelopes and pipeline_state.json…
severity Low · reachabilityundocumented· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. An operator tries a presigned https dataset URL. The run fails, and the presigned signature is kept in pipeline_state.json and in the pipeline manifest, or is emitted in the compliance bundle by --compliance-export.
Where.forgelm/compliance.py:722,forgelm/compliance.py:1540-1541,forgelm/compliance.py:1575-1579,forgelm/compliance.py:1100-1101
Recommended fix. Reject http(s) values in dataset_name_or_path and extra_datasets at config validation with an actionable message, or mask URL userinfo/query before recording a path in manifests and error strings. -
CR-A07-128 (code, Low) — AuditLogger chains past whitespace-only and other junk tail lines without parsing the last record, and the strict approval reader also skips whitespace-only lines: approve/reject/purge extend and act…
severity Low · reachabilitydefault· intentintended-but-harmful· confidence likely · independently confirmed · reproduced end to end
Failure scenario. A stray whitespace or form-feed line appears in the audit log through an editor, a log shipper or tampering. Subsequent approve, reject, purge and training resumes keep appending, and approve/reject decide normally, with no warning at write time. Only a later verify-audit reports exit 6, at the junk line.
Where.forgelm/compliance.py:326,forgelm/compliance.py:313-321,forgelm/compliance.py:2658-2660,forgelm/compliance.py:2544-2551
Recommended fix. With the shared LF-only splitter recommended above, have the writer refuse, under the same ValueError/OSError policy as the torn-tail guard, a tail whose last record is not a JSON object, and treat whitespace-only records as malformed. Make strict iter_audit_events raise on a whitespace-only line, so the approval guards and the verifier agree.
Done when
- Every finding above is fixed or closed with a reason (tick it here).
- Code fixes carry a test; doc fixes update the Turkish mirror in the same PR.
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
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
docs: close monitoring and webhook review contradictions