Skip to main content

zeroclaw_config/
validation_warnings.rs

1//! Non-fatal validation warnings — config that loads and validates
2//! successfully (i.e. `Config::validate()` returns `Ok(())`) but will fail
3//! at agent runtime because of a logical inconsistency the schema can't
4//! enforce structurally.
5//!
6//! The CLI surfaces these via `zeroclaw_log::record!` so operators see them on
7//! stderr. The gateway HTTP API surfaces them via the `warnings` field on
8//! `PropResponse` / `PatchResponse` so dashboard callers see the same
9//! signal — closing the parity gap that previously left a dashboard user
10//! with no indication their config would fail at runtime.
11//!
12//! Each warning carries:
13//! - a stable `code` (machine-friendly, matches across releases for a
14//!   given check)
15//! - a human-readable `message` (suitable for direct display to operators)
16//! - the dotted property `path` the warning concerns (so the dashboard
17//!   can highlight the offending field)
18//!
19//! Adding a new warning: append the check to `Config::collect_warnings`
20//! in `schema.rs` and pick a stable `code`. `Config::validate` emits each
21//! collected warning via `zeroclaw_log::record!` so logs continue to show them.
22
23use serde::{Deserialize, Serialize};
24
25/// One non-fatal validation issue surfaced after a successful save.
26///
27/// Stable codes (extend as new warnings are added):
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
30pub struct ValidationWarning {
31    /// Stable machine-readable identifier for the warning class.
32    pub code: String,
33    /// Human-readable description suitable for direct display.
34    pub message: String,
35    /// Dotted property path the warning concerns
36    /// (e.g. `"agents.researcher.model_provider"`).
37    pub path: String,
38}
39
40impl ValidationWarning {
41    pub fn new(
42        code: impl Into<String>,
43        message: impl Into<String>,
44        path: impl Into<String>,
45    ) -> Self {
46        Self {
47            code: code.into(),
48            message: message.into(),
49            path: path.into(),
50        }
51    }
52}