mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-12 01:45:14 +02:00
feat(cli): the operator config surface — identity and output defaults (RFC-007 PR 1)
~/.omnigraph/config.yaml joins the resolution chains as the operator surface: operator.actor becomes the last hop of THE actor chain (--as > legacy cli.actor during the RFC-008 window > operator.actor > none, one implementation for direct-engine and cluster commands alike) and defaults.output joins the read-format cascade below every more-specific source. Discovery honors $OMNIGRAPH_HOME (tilde-expanded, #139 finding 9); an absent file is an empty layer; unknown keys WARN and load (a file written for later slices must not break this CLI); malformed YAML is a loud error. The module is CLI-only — the server never reads operator config (invariant 11 by construction). $OMNIGRAPH_CONFIG becomes a first-class stand-in for --config in load_config (flag > env > ./omnigraph.yaml), one meaning in both binaries. The test harness pins hermeticity: spawned binaries get a nonexistent OMNIGRAPH_HOME by default so no test ever reads the developer's real operator config. New coverage: loader unit tests, the env-precedence matrix on load_config_in, and spawned-binary e2es for the actor chain (operator wins with no flag/legacy key; legacy outranks it; --as wins) and the format cascade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
08ce8dc34d
commit
be4bd46212
7 changed files with 445 additions and 36 deletions
|
|
@ -726,6 +726,73 @@ fn cluster_apply_uses_cli_actor_from_local_config() {
|
|||
assert_eq!(apply(&["--as", "andrew"]), "andrew", "--as overrides cli.actor");
|
||||
}
|
||||
|
||||
/// RFC-007 PR 1: the operator layer joins the actor chain —
|
||||
/// `--as` > legacy `cli.actor` (RFC-008 window) > `operator.actor` > none.
|
||||
#[test]
|
||||
fn cluster_apply_uses_operator_actor_from_omnigraph_home() {
|
||||
let temp = tempdir().unwrap();
|
||||
write_cluster_config_fixture(temp.path());
|
||||
let operator_home = tempdir().unwrap();
|
||||
fs::write(
|
||||
operator_home.path().join("config.yaml"),
|
||||
"operator:\n actor: act-operator\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let output = cli()
|
||||
.current_dir(temp.path())
|
||||
.env("OMNIGRAPH_HOME", operator_home.path())
|
||||
.arg("cluster")
|
||||
.arg("import")
|
||||
.arg("--config")
|
||||
.arg(temp.path())
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success(), "{output:?}");
|
||||
|
||||
let apply = |extra: &[&str]| {
|
||||
let mut command = cli();
|
||||
command
|
||||
.current_dir(temp.path())
|
||||
.env("OMNIGRAPH_HOME", operator_home.path());
|
||||
for arg in extra {
|
||||
command.arg(arg);
|
||||
}
|
||||
let output = command
|
||||
.arg("cluster")
|
||||
.arg("apply")
|
||||
.arg("--config")
|
||||
.arg(temp.path())
|
||||
.arg("--json")
|
||||
.output()
|
||||
.unwrap();
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_str(String::from_utf8_lossy(&output.stdout).trim()).unwrap();
|
||||
json["actor"].clone()
|
||||
};
|
||||
|
||||
// No --as, no omnigraph.yaml: the operator identity applies.
|
||||
assert_eq!(
|
||||
apply(&[]),
|
||||
"act-operator",
|
||||
"operator.actor is the no-flag, no-legacy-config default"
|
||||
);
|
||||
// --as still wins over everything.
|
||||
assert_eq!(apply(&["--as", "andrew"]), "andrew");
|
||||
|
||||
// A legacy cli.actor (RFC-008 window) outranks the operator layer.
|
||||
fs::write(
|
||||
temp.path().join("omnigraph.yaml"),
|
||||
"cli:\n actor: act-legacy\n",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
apply(&[]),
|
||||
"act-legacy",
|
||||
"legacy cli.actor wins over operator.actor during the deprecation window"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cluster_approve_uses_cli_actor_fallback() {
|
||||
let temp = tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -984,6 +984,52 @@ fn read_csv_format_outputs_header_and_row_values() {
|
|||
assert!(stdout.contains("Alice"));
|
||||
}
|
||||
|
||||
/// RFC-007 PR 1: the format cascade's operator hop — `defaults.output` in
|
||||
/// ~/.omnigraph/config.yaml applies when nothing more specific is given,
|
||||
/// and `--format` still wins over it.
|
||||
#[test]
|
||||
fn read_uses_operator_default_output_format() {
|
||||
let temp = tempdir().unwrap();
|
||||
let graph = graph_path(temp.path());
|
||||
init_graph(&graph);
|
||||
load_fixture(&graph);
|
||||
let operator_home = tempdir().unwrap();
|
||||
fs::write(
|
||||
operator_home.path().join("config.yaml"),
|
||||
"defaults:\n output: csv\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let read = |extra: &[&str]| {
|
||||
let mut command = cli();
|
||||
command
|
||||
.env("OMNIGRAPH_HOME", operator_home.path())
|
||||
.arg("read")
|
||||
.arg(&graph)
|
||||
.arg("--query")
|
||||
.arg(fixture("test.gq"))
|
||||
.arg("--name")
|
||||
.arg("get_person")
|
||||
.arg("--params")
|
||||
.arg(r#"{"name":"Alice"}"#);
|
||||
for arg in extra {
|
||||
command.arg(arg);
|
||||
}
|
||||
stdout_string(&output_success(&mut command))
|
||||
};
|
||||
|
||||
let stdout = read(&[]);
|
||||
assert!(
|
||||
stdout.lines().next().unwrap().contains("p.name") && stdout.contains("Alice"),
|
||||
"operator defaults.output: csv applies with no --format: {stdout}"
|
||||
);
|
||||
let stdout = read(&["--format", "jsonl"]);
|
||||
assert!(
|
||||
stdout.starts_with('{'),
|
||||
"--format wins over the operator default: {stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_jsonl_format_outputs_metadata_header_first() {
|
||||
let temp = tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -12,12 +12,24 @@ use reqwest::blocking::Client;
|
|||
use serde_json::Value;
|
||||
use tempfile::{TempDir, tempdir};
|
||||
|
||||
/// Hermetic default: point OMNIGRAPH_HOME at a path that exists on no
|
||||
/// machine, so spawned binaries never read the developer's real
|
||||
/// ~/.omnigraph/ (an absent operator config is an empty layer). Tests
|
||||
/// exercising the operator layer override the var explicitly.
|
||||
pub const HERMETIC_OPERATOR_HOME: &str = "/nonexistent/omnigraph-test-home";
|
||||
|
||||
pub fn cli() -> Command {
|
||||
Command::cargo_bin("omnigraph").unwrap()
|
||||
let mut command = Command::cargo_bin("omnigraph").unwrap();
|
||||
command.env("OMNIGRAPH_HOME", HERMETIC_OPERATOR_HOME);
|
||||
command.env_remove("OMNIGRAPH_CONFIG");
|
||||
command
|
||||
}
|
||||
|
||||
pub fn cli_process() -> StdCommand {
|
||||
StdCommand::new(assert_cmd::cargo::cargo_bin("omnigraph"))
|
||||
let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("omnigraph"));
|
||||
command.env("OMNIGRAPH_HOME", HERMETIC_OPERATOR_HOME);
|
||||
command.env_remove("OMNIGRAPH_CONFIG");
|
||||
command
|
||||
}
|
||||
|
||||
fn server_process() -> StdCommand {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue