diff --git a/crates/omnigraph-cli/src/main.rs b/crates/omnigraph-cli/src/main.rs index dab83d1..62fff60 100644 --- a/crates/omnigraph-cli/src/main.rs +++ b/crates/omnigraph-cli/src/main.rs @@ -1251,28 +1251,31 @@ async fn open_local_db_with_policy(graph: &ResolvedCliGraph) -> Result) -> Result> { + if let Some(actor) = cli_as { + return Ok(Some(actor.to_string())); + } + let config = load_config(None).wrap_err( + "resolving the default actor from the per-operator omnigraph.yaml (pass --as to skip this lookup)", + )?; + Ok(config.cli.actor.clone()) +} + /// Resolve the CLI's effective actor identity for engine-layer policy /// (MR-722). Precedence: `--as ` (top-level flag) overrides /// `cli.actor` from `omnigraph.yaml`; both unset returns `None`. When /// policy is configured and this returns `None`, the engine-layer /// footgun guard intentionally denies — silent bypass via "I forgot the /// actor" is what the guard prevents. -/// Actor resolution for cluster operations. Cluster FACTS stay unlayered -/// (cluster.yaml only), but the operator's identity is a per-operator fact — -/// the per-operator config's permanent job. An explicit --as never touches -/// any config (containers and CI stay config-free); without it, the standard -/// cwd omnigraph.yaml search supplies `cli.actor`, and a malformed config -/// fails loudly rather than silently dropping attribution. -fn resolve_cluster_actor(cli_as: Option<&str>) -> Result> { - if let Some(actor) = cli_as { - return Ok(Some(actor.to_string())); - } - let config = load_cli_config(None).wrap_err( - "resolving the default actor from the per-operator omnigraph.yaml (pass --as to skip this lookup)", - )?; - Ok(config.cli.actor.clone()) -} - fn resolve_cli_actor<'a>(cli_as: Option<&'a str>, config: &'a OmnigraphConfig) -> Option<&'a str> { cli_as.or(config.cli.actor.as_deref()) } diff --git a/crates/omnigraph-cli/tests/cli.rs b/crates/omnigraph-cli/tests/cli.rs index 00582a7..ab3c23b 100644 --- a/crates/omnigraph-cli/tests/cli.rs +++ b/crates/omnigraph-cli/tests/cli.rs @@ -4339,22 +4339,19 @@ fn cluster_apply_uses_cli_actor_from_local_config() { "cli:\n actor: act-local\n", ) .unwrap(); - let run = |extra: &[&str]| { - let mut command = cli(); - command.current_dir(temp.path()); - for arg in extra { - command.arg(arg); - } - let output = command - .arg("cluster") - .arg("import") - .arg("--config") - .arg(temp.path()) - .arg("--json") - .output() - .unwrap(); - assert!(output.status.success(), "{output:?}"); - // apply, capturing the echoed actor + // Phase 1: import once (setup, not under test). + let output = cli() + .current_dir(temp.path()) + .arg("cluster") + .arg("import") + .arg("--config") + .arg(temp.path()) + .output() + .unwrap(); + assert!(output.status.success(), "{output:?}"); + + // Phase 2: apply alone, capturing the echoed actor (idempotent re-runs). + let apply = |extra: &[&str]| { let mut command = cli(); command.current_dir(temp.path()); for arg in extra { @@ -4372,24 +4369,8 @@ fn cluster_apply_uses_cli_actor_from_local_config() { serde_json::from_str(String::from_utf8_lossy(&output.stdout).trim()).unwrap(); json["actor"].clone() }; - assert_eq!(run(&[]), "act-local", "cli.actor is the no-flag default"); - - // A fresh dir (state already imported above): the flag wins over config. - let mut command = cli(); - command.current_dir(temp.path()); - let output = command - .arg("--as") - .arg("andrew") - .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(); - assert_eq!(json["actor"], "andrew", "--as overrides cli.actor"); + assert_eq!(apply(&[]), "act-local", "cli.actor is the no-flag default"); + assert_eq!(apply(&["--as", "andrew"]), "andrew", "--as overrides cli.actor"); } #[test]