mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-12 01:45:14 +02:00
feat(config): per-key deprecation warnings on legacy omnigraph.yaml load (RFC-008 stage 1)
Loading a legacy file (flag, env, or cwd-found — never on defaults) emits one stderr block listing each key actually present with its destination from RFC-008's migration map — the map applied to YOUR file, not a generic banner. Once per process; both binaries warn (cluster-mode boots never reach load_config, silent by construction); suppressible via OMNIGRAPH_SUPPRESS_YAML_DEPRECATION=1 for CI logs during the window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
588b0c1b6c
commit
c89d268b23
2 changed files with 127 additions and 1 deletions
|
|
@ -498,3 +498,43 @@ fn graphs_list_against_local_uri_errors_with_remote_only_message() {
|
|||
"expected 'remote multi-graph server URL' rejection in stderr; got:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
/// RFC-008 stage 1: loading a legacy omnigraph.yaml emits the per-key
|
||||
/// deprecation block (the migration map applied to THIS file), suppressible
|
||||
/// via OMNIGRAPH_SUPPRESS_YAML_DEPRECATION.
|
||||
#[test]
|
||||
fn legacy_config_load_warns_per_key_and_suppression_silences() {
|
||||
let temp = tempdir().unwrap();
|
||||
fs::write(
|
||||
temp.path().join("omnigraph.yaml"),
|
||||
"cli:\n actor: act-x\ngraphs:\n g:\n uri: /tmp/never-opened\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// `graphs list --json` loads the config and exits without touching the
|
||||
// graph URI.
|
||||
let output = cli()
|
||||
.current_dir(temp.path())
|
||||
.arg("graphs")
|
||||
.arg("list")
|
||||
.arg("--json")
|
||||
.output()
|
||||
.unwrap();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(
|
||||
stderr.contains("deprecated (RFC-008)") && stderr.contains("`cli.actor` -> `operator.actor`"),
|
||||
"{stderr}"
|
||||
);
|
||||
assert!(stderr.contains("config migrate"), "{stderr}");
|
||||
|
||||
let output = cli()
|
||||
.current_dir(temp.path())
|
||||
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
||||
.arg("graphs")
|
||||
.arg("list")
|
||||
.arg("--json")
|
||||
.output()
|
||||
.unwrap();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(!stderr.contains("deprecated (RFC-008)"), "{stderr}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -549,7 +549,9 @@ fn load_config_in(
|
|||
});
|
||||
|
||||
let mut config = if let Some(path) = &config_path {
|
||||
serde_yaml::from_str::<OmnigraphConfig>(&fs::read_to_string(path)?)?
|
||||
let text = fs::read_to_string(path)?;
|
||||
warn_yaml_deprecation_once(path, &text);
|
||||
serde_yaml::from_str::<OmnigraphConfig>(&text)?
|
||||
} else {
|
||||
OmnigraphConfig::default()
|
||||
};
|
||||
|
|
@ -563,6 +565,74 @@ fn load_config_in(
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
/// RFC-008 stage 1: suppress the legacy-config deprecation warning
|
||||
/// (one process), for CI logs during the deprecation window.
|
||||
pub const SUPPRESS_YAML_DEPRECATION_ENV: &str = "OMNIGRAPH_SUPPRESS_YAML_DEPRECATION";
|
||||
|
||||
/// RFC-008's migration map (the "Where every key goes" table), applied to
|
||||
/// the keys actually present in a loaded file — never a generic banner.
|
||||
/// Keys are `(yaml pointer, destination)`; the pointer is matched against
|
||||
/// the file's real top-level/nested keys.
|
||||
const YAML_DEPRECATION_MAP: &[(&str, &str)] = &[
|
||||
("graphs", "cluster.yaml `graphs:` (team surface) — or flags/env for the zero-config tier"),
|
||||
("queries", "the cluster catalog (`.gq` discovery in cluster.yaml)"),
|
||||
("policy", "cluster.yaml `policies:` + `applies_to` bindings"),
|
||||
("server", "flags/env (`--bind`); meaningless under cluster boot"),
|
||||
("auth", "the operator credentials chain (`omnigraph login <server>`)"),
|
||||
("aliases", "operator `aliases:` (bindings) + catalog stored queries (content)"),
|
||||
("query", "obsolete — cluster query discovery replaced `query.roots`"),
|
||||
("project", "cluster.yaml `metadata.name`"),
|
||||
("cli.actor", "`operator.actor` in ~/.omnigraph/config.yaml"),
|
||||
("cli.output_format", "`defaults.output` in ~/.omnigraph/config.yaml"),
|
||||
("cli.table_max_column_width", "`defaults.table_max_column_width` in ~/.omnigraph/config.yaml"),
|
||||
("cli.table_cell_layout", "`defaults.table_cell_layout` in ~/.omnigraph/config.yaml"),
|
||||
("cli.graph", "explicit `--target`/`--server` (no operator default-target yet)"),
|
||||
("cli.branch", "explicit `--branch`"),
|
||||
];
|
||||
|
||||
/// Emit the per-key deprecation block once per process when a legacy
|
||||
/// `omnigraph.yaml` is actually loaded. `omnigraph config migrate`
|
||||
/// produces the split these lines describe.
|
||||
fn warn_yaml_deprecation_once(path: &Path, text: &str) {
|
||||
static WARNED: std::sync::OnceLock<()> = std::sync::OnceLock::new();
|
||||
if env::var_os(SUPPRESS_YAML_DEPRECATION_ENV).is_some() {
|
||||
return;
|
||||
}
|
||||
let lines = yaml_deprecation_lines(text);
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
WARNED.get_or_init(|| {
|
||||
eprintln!(
|
||||
"warning: '{}' is deprecated (RFC-008) — its keys have new homes; run `omnigraph config migrate` for the split, set {SUPPRESS_YAML_DEPRECATION_ENV}=1 to silence:",
|
||||
path.display()
|
||||
);
|
||||
for line in &lines {
|
||||
eprintln!(" {line}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn yaml_deprecation_lines(text: &str) -> Vec<String> {
|
||||
let Ok(mapping) = serde_yaml::from_str::<serde_yaml::Mapping>(text) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let present = |pointer: &str| -> bool {
|
||||
match pointer.split_once('.') {
|
||||
None => mapping.contains_key(pointer),
|
||||
Some((outer, inner)) => mapping
|
||||
.get(outer)
|
||||
.and_then(|value| value.as_mapping())
|
||||
.is_some_and(|nested| nested.contains_key(inner)),
|
||||
}
|
||||
};
|
||||
YAML_DEPRECATION_MAP
|
||||
.iter()
|
||||
.filter(|(pointer, _)| present(pointer))
|
||||
.map(|(pointer, destination)| format!("`{pointer}` -> {destination}"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn absolute_base_dir(cwd: &Path, path: &Path) -> Result<PathBuf> {
|
||||
let path = if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
|
|
@ -608,6 +678,22 @@ mod tests {
|
|||
assert_eq!(config.cli.actor.as_deref(), Some("act-env"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yaml_deprecation_lines_name_present_keys_only() {
|
||||
let lines = super::yaml_deprecation_lines(
|
||||
"graphs:\n g:\n uri: /tmp/x\ncli:\n actor: a\n branch: main\n",
|
||||
);
|
||||
let joined = lines.join("\n");
|
||||
assert!(joined.contains("`graphs` ->"), "{joined}");
|
||||
assert!(joined.contains("`cli.actor` -> `operator.actor`"), "{joined}");
|
||||
assert!(joined.contains("`cli.branch` ->"), "{joined}");
|
||||
assert!(!joined.contains("`aliases`"), "{joined}");
|
||||
assert!(!joined.contains("`cli.output_format`"), "{joined}");
|
||||
|
||||
assert!(super::yaml_deprecation_lines("").is_empty());
|
||||
assert!(super::yaml_deprecation_lines("not: [valid").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_config_reads_yaml_defaults_from_current_dir() {
|
||||
let temp = tempdir().unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue