From b1cbb9619768cdbede252a0e8a8674c444567d09 Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Mon, 1 Jun 2026 21:55:05 +0200 Subject: [PATCH] fix(cli): honor server graph for policy tooling --- crates/omnigraph-cli/src/main.rs | 46 ++++++++++++++--------- crates/omnigraph-server/src/config.rs | 53 +++++++++++++++++++++++++++ docs/user/cli-reference.md | 2 +- docs/user/policy.md | 4 ++ 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/crates/omnigraph-cli/src/main.rs b/crates/omnigraph-cli/src/main.rs index cc73f3f..879f070 100644 --- a/crates/omnigraph-cli/src/main.rs +++ b/crates/omnigraph-cli/src/main.rs @@ -800,21 +800,17 @@ struct ResolvedPolicyContext { } fn resolve_policy_context(config: &OmnigraphConfig) -> Result { - let selected = config.cli_graph_name().map(str::to_string); - config.resolve_graph_selection(selected.as_deref())?; + let selected = config.resolve_policy_tooling_graph_selection()?; let policy_file = config - .resolve_policy_file_for(selected.as_deref()) + .resolve_policy_file_for(selected) .ok_or_else(|| { color_eyre::eyre::eyre!( "policy.file or graphs..policy.file must be set in omnigraph.yaml" ) })?; - let graph_id = match selected.as_deref() { + let graph_id = match selected { Some(name) => graph_resource_id_for_selection(Some(name), ""), - None => { - let anonymous_graph_id = anonymous_policy_graph_id(config)?; - graph_resource_id_for_selection(None, &anonymous_graph_id) - } + None => graph_resource_id_for_selection(None, "default"), }; Ok(ResolvedPolicyContext { policy_file, @@ -871,16 +867,6 @@ fn normalize_policy_graph_uri(uri: &str) -> Result { } } -fn anonymous_policy_graph_id(config: &OmnigraphConfig) -> Result { - let raw_uri = config - .resolve_target_uri(None, None, config.server_graph_name()) - .or_else(|_| config.resolve_target_uri(None, None, config.cli_graph_name())); - match raw_uri { - Ok(uri) => normalize_policy_graph_uri(&uri), - Err(_) => Ok("default".to_string()), - } -} - fn resolve_remote_bearer_token( config: &OmnigraphConfig, explicit_uri: Option<&str>, @@ -3483,6 +3469,30 @@ server: assert!(context.policy_file.ends_with("server-policy.yaml")); } + #[test] + fn graph_identity_resolve_policy_context_anonymous_uses_top_level_default_identity() { + let temp = tempdir().unwrap(); + let config_path = temp.path().join("omnigraph.yaml"); + fs::write( + &config_path, + r#" +project: + name: misleading-project +graphs: + local: + uri: /tmp/local-policy-graph.omni +policy: + file: ./top-policy.yaml +"#, + ) + .unwrap(); + + let config = load_config(Some(&config_path)).unwrap(); + let context = resolve_policy_context(&config).unwrap(); + assert_eq!(context.graph_id, "default"); + assert!(context.policy_file.ends_with("top-policy.yaml")); + } + #[test] fn graph_identity_resolve_cli_graph_named_target_uses_graph_key_not_project_name_or_uri() { let temp = tempdir().unwrap(); diff --git a/crates/omnigraph-server/src/config.rs b/crates/omnigraph-server/src/config.rs index 3bab877..b308b72 100644 --- a/crates/omnigraph-server/src/config.rs +++ b/crates/omnigraph-server/src/config.rs @@ -364,6 +364,10 @@ impl OmnigraphConfig { } } + pub fn resolve_policy_tooling_graph_selection(&self) -> Result> { + self.resolve_graph_selection(self.cli_graph_name().or_else(|| self.server_graph_name())) + } + /// The policy file that applies for a graph selection — the policy /// sibling of [`OmnigraphConfig::query_entries_for`], so policy and /// queries resolve by the same identity rule. A named graph in @@ -692,6 +696,55 @@ policy: {} ); } + #[test] + fn policy_tooling_graph_selection_prefers_cli_then_server_and_validates() { + let temp = tempdir().unwrap(); + fs::write( + temp.path().join("omnigraph.yaml"), + "graphs:\n local:\n uri: ./local.omni\n prod:\n uri: ./prod.omni\n\ + server:\n graph: local\ncli:\n graph: prod\n", + ) + .unwrap(); + let config = load_config_in(temp.path(), None).unwrap(); + assert_eq!( + config.resolve_policy_tooling_graph_selection().unwrap(), + Some("prod") + ); + + let temp = tempdir().unwrap(); + fs::write( + temp.path().join("omnigraph.yaml"), + "graphs:\n local:\n uri: ./local.omni\nserver:\n graph: local\n", + ) + .unwrap(); + let config = load_config_in(temp.path(), None).unwrap(); + assert_eq!( + config.resolve_policy_tooling_graph_selection().unwrap(), + Some("local") + ); + + let temp = tempdir().unwrap(); + fs::write(temp.path().join("omnigraph.yaml"), "policy: {}\n").unwrap(); + let config = load_config_in(temp.path(), None).unwrap(); + assert_eq!(config.resolve_policy_tooling_graph_selection().unwrap(), None); + + let temp = tempdir().unwrap(); + fs::write( + temp.path().join("omnigraph.yaml"), + "graphs:\n local:\n uri: ./local.omni\nserver:\n graph: ghost\n", + ) + .unwrap(); + let config = load_config_in(temp.path(), None).unwrap(); + let err = config + .resolve_policy_tooling_graph_selection() + .unwrap_err() + .to_string(); + assert!( + err.contains("ghost") && err.contains("not found"), + "unknown server.graph must use graph-selection validation: {err}" + ); + } + #[test] fn resolve_query_path_searches_config_roots() { let temp = tempdir().unwrap(); diff --git a/docs/user/cli-reference.md b/docs/user/cli-reference.md index 7155d0f..019e4ad 100644 --- a/docs/user/cli-reference.md +++ b/docs/user/cli-reference.md @@ -24,7 +24,7 @@ A reference for the `omnigraph` binary's command surface and `omnigraph.yaml` sc | `optimize` | non-destructive Lance compaction | | `cleanup --keep N --older-than 7d --confirm` | destructive version GC | | `embed` | offline JSONL embedding pipeline | -| `policy validate \| test \| explain` | Cedar tooling | +| `policy validate \| test \| explain` | Cedar tooling. Selects `cli.graph`, else `server.graph`, else top-level `policy.file` | | `version` / `-v` | print `omnigraph 0.3.x` | ## `omnigraph.yaml` schema diff --git a/docs/user/policy.md b/docs/user/policy.md index 77d6aed..ec0d214 100644 --- a/docs/user/policy.md +++ b/docs/user/policy.md @@ -98,6 +98,10 @@ bearer token. ## CLI +Policy tooling resolves its graph like server single-mode policy: `cli.graph` +wins, otherwise `server.graph` is used, otherwise the top-level `policy.file` +is validated/tested/explained as the anonymous policy. + - `omnigraph policy validate` — parse + count actors, exit 1 on parse error. - `omnigraph policy test` — run cases in `policy.tests.yaml`, exit 1 on any expectation mismatch. - `omnigraph policy explain --actor … --action … [--branch …] [--target-branch …]` — show decision and matched rule.