Skip to content

MonitoringConfig is inert: no exporter, drift alert or schedule exists, yet the manifest and Annex IV §6 record it as configured (and '$None' when no endpoint is set) (CR-A01-007) #93

Closed

Description

@cemililik

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

Summary

An operator enables monitoring with metrics_export: prometheus and alert_on_drift: true. The Annex IV bundle states that post-market monitoring is configured, but no hook, export or alert ever runs.

Primary finding

CR-A01-007 — MonitoringConfig is inert: no exporter, drift alert or schedule exists, yet the manifest and Annex IV §6 record it as configured (and '$None' when no endpoint is set)

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

What happens. MonitoringConfig (forgelm/config.py:1279-1298) describes post-market monitoring hooks, Prometheus/Datadog/custom exporters, drift alerts and a check interval. Its only runtime consumer, generate_training_manifest (compliance.py:1581-1589), copies the fields into manifest['monitoring']. That becomes the Annex IV monitoring_and_logging section (compliance.py:1857). Nothing posts, exports, detects drift or schedules checks. mon_cfg.endpoint or f"${mon_cfg.endpoint_env}" records '$None' when neither field is set, and lets endpoint win over endpoint_env, contrary to the field description. The QMS SOP and the docs cite drift alerts that cannot fire.

Where.

Evidence.

  • rg for monitoring/metrics_export/alert_on_drift/check_interval_hours outside config.py finds only compliance.py:1582-1588, the Annex IV section mapping, model_card.py:296 (exclusion) and the wizard. Dynamic: generate_training_manifest(cfg with monitoring.enabled only) returned endpoint '$None'. With endpoint='https://a' and endpoint_env='MON_URL' it returned 'https://a'.
  • run1 (monitoring{enabled:true} only): compliance_report.json monitoring = {'endpoint': '$None', 'metrics_export': 'none', 'alert_on_drift': True, 'check_interval_hours': 24}; annex_iv_metadata.json:76-81 identical.
  • rg for monitoring consumers in forgelm/ finds only compliance.py:1582-1589 (manifest copy), compliance.py:1857 (Annex IV section), model_card.py:296 (exclusion) and the wizard collector. There is no safe_post or other HTTP call that uses mon_cfg.

Recommended fix. Mark the block as declarative metadata in its description and docs (no runtime hooks), fix the '$None' rendering and the endpoint/endpoint_env precedence, or implement the hooks.

Other findings with the same root cause

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

  • TECH-T09-003 (technology, Now) — Experiment tracking, webhooks and monitoring: monitoring.metrics_export prometheus / datadog / custom_webhook with no exporter (forgelm/config.py:1291) (incorrect-claim)
    severity Now
    Item. monitoring.metrics_export prometheus / datadog / custom_webhook with no exporter (forgelm/config.py:1291) · verdict: incorrect-claim · urgency: Now
    State in ForgeLM. forgelm/config.py:1291-1294 accepts none/prometheus/datadog/custom_webhook. Outside the wizard (forgelm/wizard/_collectors.py:894-903, _orchestrator.py:646), the only consumer is forgelm/compliance.py:1582-1589. It copies endpoint, metrics_export, alert_on_drift and check_interval_hours into the compliance manifest, and compliance.py:1857 puts them into the Annex IV §6 'monitoring_and_logging' fi…
    State of the ecosystem on 2026-10-01. Prometheus pulls by scraping. Its docs say 'Usually, the only valid use case for the Pushgateway is for capturing the outcome of a service-level batch job'. The Pushgateway takes PUT/POST to /metrics/job/ in the text (or protobuf) exposition format, not arbitrary JSON. Prometheus also has an opt-in OTLP receiver (--web.enable-otlp-receiver, /api/v1/otlp/v1/metrics). Datadog takes metrics thr…
    Recommendation. Now: rewrite the Field descriptions, config_template comment, both configuration references and the site/wizard copy in all languages to say these fields are declarative metadata recorded in the compliance manifest, and that ForgeLM exports nothing and has no drift detector. Add a config-load warning when monitoring.enabled is true and metrics_export is not none. Next: either implement a real exp…
    Sources.

  • DOC-D1B01-004 (docs, Critical) — docs/reference/configuration.md: false claim — monitoring (EU AI Act Art. 12+17): enabled "Enable monitoring hooks", endpoint "Monitoring webhook URL", metrics_export prometheus/datadog/custo…
    severity Critical
    Where. docs/reference/configuration.md:360-369 (Turkish mirror: docs/reference/configuration-tr.md:302-311) · type: false claim
    The document says. monitoring (EU AI Act Art. 12+17): enabled "Enable monitoring hooks", endpoint "Monitoring webhook URL", metrics_export prometheus/datadog/custom_webhook, alert_on_drift "Alert on model drift", check_interval_hours "Monitoring check interval"
    What the code does. The block is metadata only. When enabled it is copied into the compliance manifest's 'monitoring' section (compliance.py:1582-1589). No code contacts the endpoint, exports metrics, detects drift, sends alerts or schedules checks; apart from the wizard prompts there is no other consumer in forgelm/.
    Code references. forgelm/compliance.py:1582-1589, forgelm/config.py:1279-1300
    Proposed correction. Fix the code (implement the hooks) or make the schema explicitly declarative. Temporary doc text: "The monitoring block is declarative metadata: when enabled: true it is copied into the compliance manifest so the Annex IV bundle documents your post-market monitoring plan. ForgeLM itself does not contact the endpoint, export metrics, detect dri…
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-D3B04A-002 (docs, Critical) — docs/usermanuals/en/reference/configuration.md: false claim — monitoring: — 'Enable Article 12 post-market monitoring', endpoint 'Monitoring webhook URL', metrics_export 'none | prometheus | datado…
    severity Critical
    Where. docs/usermanuals/en/reference/configuration.md:280-287 (Turkish mirror: docs/usermanuals/tr/reference/configuration.md:281-288) · type: false claim
    The document says. monitoring: — 'Enable Article 12 post-market monitoring', endpoint 'Monitoring webhook URL', metrics_export 'none | prometheus | datadog | custom_webhook', alert_on_drift 'webhook alert on drift-detector regression', check_interval_hours 'monitoring cadence in hours'.
    What the code does. The monitoring block is declarative metadata only. When enabled=true its values are copied into the training manifest and surfaced in the Annex IV monitoring_and_logging section. No code exports metrics, contacts the endpoint, runs a drift detector, sends an alert, or schedules checks. The only consumers are the manifest writer and the model-card exclusion list.
    Code references. forgelm/compliance.py:1581-1589, forgelm/compliance.py:1856-1857, forgelm/model_card.py:296, forgelm/config.py:1279-1298
    Proposed correction. Add above the block: "monitoring: is declarative metadata: ForgeLM records these values in the compliance manifest and the Annex IV monitoring_and_logging section so your technical documentation names your monitoring setup. ForgeLM itself does not export metrics, contact the endpoint, detect drift, send alerts or run scheduled checks — operate…
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-BLEGAL-048 (docs, High) — site/js/translations.js: false claim — [IS-T09-19] 'Post-market monitoring (Article 12+17) wires drift detection into Prometheus / Datadog after deployment.' (TR :2076, DE :2471, FR :2866, ES :3261,…
    severity High
    Where. site/js/translations.js:1681 · type: false claim
    The document says. [IS-T09-19] 'Post-market monitoring (Article 12+17) wires drift detection into Prometheus / Datadog after deployment.' (TR :2076, DE :2471, FR :2866, ES :3261, :3656)
    What the code does. monitoring.metrics_export (forgelm/config.py:1291) has no exporter, and no drift detector exists. Prometheus pulls by scraping (the Pushgateway takes the exposition format, not JSON), and Datadog takes DogStatsD, /api/v2/series or OTel; neither accepts ForgeLM's webhook JSON. Legally, the post-market monitoring system and plan are AI Act Art. 72, reflected in the QMS at Art. 17(1)(h). Art. 12 onl…
    Proposed correction. Monitoring (monitoring.*) records an endpoint for your own post-market monitoring stack (AI Act Art. 72); ForgeLM does not export metrics or detect drift. Apply the same wording to all six locales.

  • DOC-D1B05-003 (docs, High) — docs/qms/sop_incident_response.md: false claim — Incidents may be detected by: Runtime monitoring alerts (if monitoring.alert_on_drift: true)
    severity High
    Where. docs/qms/sop_incident_response.md:31 (Turkish mirror: docs/qms/sop_incident_response-tr.md:31) · type: false claim
    The document says. Incidents may be detected by: Runtime monitoring alerts (if monitoring.alert_on_drift: true)
    What the code does. ForgeLM has no drift detector and emits no monitoring alert. monitoring.alert_on_drift is only copied into the compliance manifest (when monitoring.enabled), although its field description says 'Emit a webhook alert when drift detector flags a regression'.
    Code references. forgelm/config.py:1295-1297, forgelm/compliance.py:1582-1589
    Proposed correction. Fix the code (implement or remove the alert behaviour and correct the field description). Interim doc text: "Runtime monitoring alerts from your own serving/monitoring stack (ForgeLM records monitoring.* settings in the compliance manifest but does not itself detect drift or emit alerts)."
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-D4B02-003 (docs, High) — config_template.yaml: misleading — Post-Market Monitoring block (enabled, endpoint_env, metrics_export: prometheus, alert_on_drift: true, check_interval_hours: 24) presented as an active monitoring f…
    severity High
    Where. config_template.yaml:258-264 · type: misleading
    The document says. Post-Market Monitoring block (enabled, endpoint_env, metrics_export: prometheus, alert_on_drift: true, check_interval_hours: 24) presented as an active monitoring feature
    What the code does. The monitoring block is metadata only. compliance.py copies endpoint, metrics_export, alert_on_drift and check_interval_hours into the compliance manifest (Annex IV §6). No code exports metrics, polls on an interval, detects drift or sends alerts, and endpoint_env is never resolved to send anything.
    Code references. forgelm/compliance.py:1581-1589, forgelm/compliance.py:1856-1857, forgelm/config.py:1279-1298
    Proposed correction. Fix the code, or narrow the promise. Temporary doc note: "# Recorded in the Annex IV manifest only — ForgeLM does not export metrics, poll, or send drift alerts; wire your own monitoring stack."

  • DOC-D4B09-008 (docs, High) — site/js/translations.js: false claim — 'Audit log entries record every dispatch — including failures — so you have an independent trail of what was sent'
    severity High
    Where. site/js/translations.js:1778 (wizard.webhook.enabled.detail) · type: false claim
    The document says. 'Audit log entries record every dispatch — including failures — so you have an independent trail of what was sent'
    What the code does. No audit event is emitted for webhook dispatches. The only log_event emitters are compliance/pipeline/safety/model events, and the audit catalog's webhook section only correlates webhook events to audit events (docs/reference/audit_event_catalog.md:150-170). Failed or successful posts appear only in the process log.
    Code references. forgelm/webhook.py:102-240, docs/reference/audit_event_catalog.md:150-170
    Proposed correction. Delete the sentence, or write: 'Delivery failures are logged as warnings in the process log only; the audit log records the underlying training events (training.started, model.reverted, human_approval.required, and so on) that the webhooks mirror.'

  • DOC-D4B09-009 (docs, High) — site/js/translations.js: false claim — Post-market monitoring 'wires training metrics into Prometheus / Datadog / a custom webhook'; 'wires the right hooks into the training run so your existing obse…
    severity High
    Where. site/js/translations.js:1783-1796 (wizard.monitoring.hint/.enabled.hint/.enabled.detail/.endpoint.hint/.alert_on_drift.hint), :1681 (wizard.step.operations.tutorial) · type: false claim
    The document says. Post-market monitoring 'wires training metrics into Prometheus / Datadog / a custom webhook'; 'wires the right hooks into the training run so your existing observability stack picks them up'; alert_on_drift 'emits a webhook alert when the drift detector flags a regression'; 'wires drift detection i…
    What the code does. The monitoring block is only copied into the compliance manifest (forgelm/compliance.py:1581-1589). No code pushes metrics to the endpoint, exports Prometheus or Datadog metrics, runs a drift detector, honours check_interval_hours, or sends a drift alert. Grep finds no consumer of metrics_export, alert_on_drift or the endpoint outside config.py and compliance.py.
    Code references. forgelm/compliance.py:1581-1589, forgelm/config.py:1279-1298
    Proposed correction. Describe it as: 'Declares your post-market monitoring endpoint and settings in the Annex IV manifest. ForgeLM does not push metrics or detect drift itself; wire your own observability stack.' Drop 'drift detector' wording.

  • DOC-BFIELD-017 (docs, Medium) — forgelm/config.py: false claim — Enable Article 12 post-market monitoring hooks; monitoring-system webhook URL; metrics exporter prometheus/datadog/custom_webhook; emit a webhook alert when drift det…
    severity Medium
    Where. forgelm/config.py:1284-1298 (MonitoringConfig.enabled/endpoint/metrics_export/alert_on_drift/check_interval_hours, F23 F24 F26 F27 F28) · type: false claim
    The document says. Enable Article 12 post-market monitoring hooks; monitoring-system webhook URL; metrics exporter prometheus/datadog/custom_webhook; emit a webhook alert when drift detector flags a regression; monitoring check cadence in hours
    What the code does. The only runtime consumer is compliance.py:1582-1589. It copies the fields into manifest['monitoring'] when enabled is true. No exporter, no POST to the endpoint, no drift detector and no scheduled check exist anywhere in forgelm/. A grep for drift finds only data-audit schema drift. The block is declarative metadata for the Annex IV §6 section (compliance.py:1857).
    Code references. forgelm/compliance.py:1581-1589, forgelm/compliance.py:1856-1857
    Proposed correction. Reword every MonitoringConfig description as 'recorded in the compliance manifest / Annex IV §6; ForgeLM does not run monitoring'. Example: alert_on_drift = 'Declared in the manifest: whether the deployer's monitoring system alerts on drift.'

  • DOC-D3B04A-014 (docs, Medium) — docs/usermanuals/en/reference/configuration.md: false claim — endpoint_env: null # env var overriding endpoint; '*_env fields name an environment variable that is read directly via os.environ'.
    severity Medium
    Where. docs/usermanuals/en/reference/configuration.md:283 (and :193 'read directly via os.environ') (Turkish mirror: docs/usermanuals/tr/reference/configuration.md:284 (and :193)) · type: false claim
    The document says. endpoint_env: null # env var overriding endpoint; '*_env fields name an environment variable that is read directly via os.environ'.
    What the code does. The manifest records endpoint or "$" + endpoint_env, so endpoint wins whenever it is set. The variable is never read; only the literal string '$NAME' is stored. The schema description ('overrides endpoint when set') contradicts the implementation.
    Code references. forgelm/compliance.py:1585, forgelm/config.py:1288-1290
    Proposed correction. "endpoint_env: null # name of an env var; recorded as \"$NAME\" in the manifest only when endpoint is empty (the variable is not read)". Also qualify :193: "… except monitoring.endpoint_env, which is recorded by name only."
    Turkish mirror. the Turkish page has the same error; fix both in the same PR

  • DOC-BFIELD-031 (docs, Low) — forgelm/config.py: wrong detail — Env var name carrying the endpoint URL (overrides endpoint when set).
    severity Low
    Where. forgelm/config.py:1288-1290 (MonitoringConfig.endpoint_env, F25) (Turkish mirror: docs/usermanuals/en/reference/configuration.md:283) · type: wrong detail
    The document says. Env var name carrying the endpoint URL (overrides endpoint when set).
    What the code does. compliance.py:1585 writes mon_cfg.endpoint or f"${mon_cfg.endpoint_env}", so endpoint wins and endpoint_env is never resolved. Reproduced: with both fields set, the manifest endpoint was the literal URL. With only endpoint_env set, the manifest recorded the string '$MON_URL'. When both fields are empty it records '$None'.
    Code references. forgelm/compliance.py:1585
    Proposed correction. 'Env var name recorded as the endpoint reference when endpoint is empty (not resolved).'

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.

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    dependenciesDependency or ecosystem currency
    severity: criticalBreaks a stated security or safety contract
    source: review-2026-10Found by the October 2026 code and documentation review
    urgency: nowTechnology currency: act now
    wave: 0Roadmap wave 0: immediate (patch release)
    on Oct 9, 2026
  2. self-assigned this
    on Oct 15, 2026
  3. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    I am working on all findings in this issue from development (074df5f) on fix/93-monitoring-metadata.

    Confirmed with real ForgeConfig / generate_training_manifest calls: enabling monitoring without an endpoint records $None; setting both endpoint fields records the literal URL despite the documented endpoint_env priority. Runtime searches still find no exporter, drift detector, alerts, or scheduler.

    Additional related finding: site/js/wizard.js forces monitoring on for strict risk tiers, disables its toggle, and rejects an empty endpoint when explicitly enabled. The Python schema/CLI allow the metadata plan without an endpoint; the browser imposes an unsupported runtime requirement and produces different configuration semantics.

    I will take the issue's declarative-metadata option: explicitly label the manifest/Annex IV declaration, retain the existing YAML fields, warn on enabled exporter declarations, record an absent endpoint as JSON null, and honor endpoint_env as an unresolved $NAME reference with precedence over the literal endpoint. I will align CLI/web wizard behavior, all six site locales, EN/TR references/manuals/QMS, and the webhook audit-delivery wording, with regression coverage and the full validation gauntlet before opening a PR against development.

  4. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    During browser verification I also reproduced the pre-existing Compliance-step crash: ReferenceError: appendDetail is not defined in makeRequiredTextarea prevents the browser flow from reaching Operations. This is already tracked separately as #879 (F-W1-SITE-01), so it is not folded into the monitoring contract repair.

    Confirmed the same render failure against both unmodified development (074df5f) and this working branch. The Operations renderer itself completes with an enabled plan and empty endpoint. Monitoring opt-in/empty-endpoint/env-reference behavior is being checked directly in the real JS functions and in an isolated Operations browser preview; six generated configurations have also passed the real Python schema. The existing #879 remains an independent limitation of the full browser wizard.

  5. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    Implemented in #946, targeting development from fix/93-monitoring-metadata (commit 7b42962). The PR description maps all 12 finding IDs to their resolution.

    The fix follows the declarative-metadata option: artifacts carry declarative_only: true, absent endpoints are null, non-empty endpoint_env takes precedence as an unresolved $NAME reference, and enabled exporter declarations warn. CLI/web wizard semantics, all six site locales, EN/TR references/manuals/QMS, and the webhook-delivery wording are aligned. No runtime exporter, detector, alert sender, or scheduler is claimed.

    Validation: 19 added regression cases; full local gauntlet passed with 5,114 tests passed, 22 existing skips, 87% coverage, all 31 guards, mypy, dry-run, Ruff, and the Bandit gate. Six browser-generated configs validated through the real Python schema; a real Brave preview confirmed the high-risk monitoring toggle remains editable and an enabled declaration accepts an empty endpoint.

    The separate full-browser Compliance-step crash remains tracked in #879; it was reproduced on unmodified development and is explicitly documented in the PR's verification limits.

  6. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    Review follow-up implemented in PR #946, with four new commits preserving review history:
    2f602b6, b03fea1, b70f1fe, and d138664 (no amended commits or force-push).

    Additional findings verified and corrected:

    New follow-ups with acceptance criteria, all added to the project board:

    Other pre-existing findings were mapped to existing issues instead of duplicated: #573 / #656 (verifier completeness), #658 (audit-reference projection), #661 (webhook precedence), #879 (Compliance renderer crash), #622 (bilingual skill policy), #546 (config audit-event claims), #689 (mutable-state guidance), #902 (locale backlog), and #904 (trainer split/headroom). The PR contains the complete disposition table.

    Validation at final head d138664: 5,140 Python tests passed, 22 pre-existing skips, 87% coverage, 22 Node tests, and all 31 canonical guards pass locally. The final docs-only correction also passed the relevant doc guards and manual rebuild/check. All six required CI checks pass on d138664 (lint, validate including Node, and Python 3.10–3.13), plus user-manual validation: https://github.com/HodeTech/ForgeLM/actions/runs/38794038197.

    Native Brave checks used the actual Operations renderer because the pre-existing #879 blocks complete wizard navigation. Empty endpoints, invalid env names/intervals, a valid spaced env reference with a 48-hour interval, and disabling the plan were checked. The three mutations that survived the earlier review are now all killed by the regression tests in an isolated, subsequently removed worktree.

  7. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    Second-review corrections are pushed in e020d81 and 88d04f9 (PR #946).

    Verified against current code and real exports:

    Validation at 88d04f9: full canonical local gauntlet passed — 5,169 Python tests, 22 existing skips, 87% coverage, all 31 guards, 29 Node tests and 256 real-schema YAML cases. Eight targeted JS mutations were killed in isolated temporary copies. Native Brave verified unchecked drift, schemeless rejection, IPv6/port acceptance and live error clearing in the actual Operations renderer; the separate full-wizard crash remains #879.

    One verified repository-wide test-coverage gap is filed separately as #951: required CI commands can acquire a failure-masking || true wrapper without the existing wiring suite detecting it. An isolated archive mutation confirmed this; the real workflow is unchanged. The issue has acceptance criteria and is on the project board. Other existing follow-ups remain linked from the PR.

    PR #946 still closes only #93 and #231 on merge. #74 / #100 and the follow-ups remain references. The PR records a next-minor release constraint for the breaking input acceptance changes; no release or merge has been performed. Final head 521d242 adds only prose wrapping, with whitespace-normalized content unchanged. All six required hosted checks now pass at that exact head, including the full Python 3.10–3.13 matrix; Python 3.13 confirms 5,169 passed / 22 skipped / 87% coverage. User-manual validation and CodeRabbit also pass. The reviewer closed the addressed prose-format thread; no review conversation remains unresolved. Final CI: https://github.com/HodeTech/ForgeLM/actions/runs/38806236649.

  8. added a commit that references this issue on Oct 17, 2026
    7b42962
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: configCode area: configbugSomething isn't workingdependenciesDependency or ecosystem currencydocumentationImprovements or additions to documentationseverity: criticalBreaks a stated security or safety contractsource: review-2026-10Found by the October 2026 code and documentation reviewurgency: nowTechnology currency: act nowwave: 0Roadmap wave 0: immediate (patch release)

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions