diff --git a/crates/omnigraph-cli/src/cli.rs b/crates/omnigraph-cli/src/cli.rs index a9a5d0b..ec0da08 100644 --- a/crates/omnigraph-cli/src/cli.rs +++ b/crates/omnigraph-cli/src/cli.rs @@ -31,10 +31,10 @@ pub(crate) struct Cli { #[arg(long = "as", global = true, value_name = "ACTOR")] pub(crate) as_actor: Option, - /// Target an operator-defined server by name (RFC-007): resolves to - /// its `url` from `servers:` in ~/.omnigraph/config.yaml. Exclusive - /// with a positional URI or `--target`. - #[arg(long, global = true, value_name = "NAME")] + /// Address a server by name (resolves to its `url` from `servers:` in + /// ~/.omnigraph/config.yaml) or by a literal `http(s)://` URL. Exclusive + /// with a positional URI. + #[arg(long, global = true, value_name = "NAME|URL")] pub(crate) server: Option, /// Graph id on a multi-graph `--server` (appends `/graphs/` to @@ -52,7 +52,7 @@ pub(crate) struct Cli { /// Address a single graph's storage directly (RFC-011): a `file://` / /// `s3://` store URI. Explicit, ad-hoc direct access — bypasses any - /// server. Exclusive with a positional URI / `--target` / `--server`. + /// server. Exclusive with a positional URI / `--server`. #[arg(long, global = true, value_name = "URI")] pub(crate) store: Option, @@ -76,8 +76,6 @@ pub(crate) enum Command { #[arg(hide = true)] legacy_uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long, conflicts_with_all = ["query", "query_string"])] alias: Option, @@ -114,8 +112,6 @@ pub(crate) enum Command { #[arg(hide = true)] legacy_uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long, conflicts_with_all = ["query", "query_string"])] alias: Option, @@ -140,8 +136,6 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] data: PathBuf, @@ -165,8 +159,6 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] data: PathBuf, @@ -189,8 +181,6 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] branch: Option, @@ -202,8 +192,6 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] branch: Option, @@ -250,12 +238,10 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, /// Cluster directory or storage-root URI; with --cluster-graph, resolves /// the graph's storage URI from the served cluster state. - #[arg(long, conflicts_with_all = ["uri", "target"], requires = "cluster_graph")] + #[arg(long, conflicts_with = "uri", requires = "cluster_graph")] cluster: Option, /// Graph id within --cluster. #[arg(long, requires = "cluster")] @@ -268,12 +254,10 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, /// Cluster directory or storage-root URI; with --cluster-graph, resolves /// the graph's storage URI from the served cluster state. - #[arg(long, conflicts_with_all = ["uri", "target"], requires = "cluster_graph")] + #[arg(long, conflicts_with = "uri", requires = "cluster_graph")] cluster: Option, /// Graph id within --cluster. #[arg(long, requires = "cluster")] @@ -294,12 +278,10 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, /// Cluster directory or storage-root URI; with --cluster-graph, resolves /// the graph's storage URI from the served cluster state. - #[arg(long, conflicts_with_all = ["uri", "target"], requires = "cluster_graph")] + #[arg(long, conflicts_with = "uri", requires = "cluster_graph")] cluster: Option, /// Graph id within --cluster. #[arg(long, requires = "cluster")] @@ -333,8 +315,6 @@ pub(crate) enum Command { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] query: PathBuf, @@ -489,8 +469,6 @@ pub(crate) enum GraphsCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] json: bool, @@ -505,8 +483,6 @@ pub(crate) enum BranchCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] from: Option, @@ -520,8 +496,6 @@ pub(crate) enum BranchCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] json: bool, @@ -532,8 +506,6 @@ pub(crate) enum BranchCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, name: String, #[arg(long)] @@ -545,8 +517,6 @@ pub(crate) enum BranchCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, source: String, #[arg(long)] @@ -563,8 +533,6 @@ pub(crate) enum SchemaCommand { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] schema: PathBuf, @@ -581,8 +549,6 @@ pub(crate) enum SchemaCommand { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] schema: PathBuf, @@ -606,8 +572,6 @@ pub(crate) enum SchemaCommand { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] json: bool, @@ -622,8 +586,6 @@ pub(crate) enum CommitCommand { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] branch: Option, @@ -636,8 +598,6 @@ pub(crate) enum CommitCommand { #[arg(long)] uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, commit_id: String, #[arg(long)] @@ -684,16 +644,12 @@ pub(crate) enum QueriesCommand { /// Graph URI uri: Option, #[arg(long)] - target: Option, - #[arg(long)] config: Option, #[arg(long)] json: bool, }, /// List the registered stored queries (name, MCP exposure, params). List { - #[arg(long)] - target: Option, #[arg(long)] config: Option, #[arg(long)] diff --git a/crates/omnigraph-cli/src/client.rs b/crates/omnigraph-cli/src/client.rs index 5d52678..5c427f2 100644 --- a/crates/omnigraph-cli/src/client.rs +++ b/crates/omnigraph-cli/src/client.rs @@ -66,6 +66,19 @@ pub(crate) enum GraphClient { }, } +/// A remote graph must be addressed with `--server` (RFC-011): a positional or +/// `--uri` `http(s)://` URL no longer auto-dispatches to a server. A remote URL +/// produced by a server scope (`via_server`) is fine. +fn reject_positional_remote(via_server: bool, uri: &str) -> Result<()> { + if !via_server && is_remote_uri(uri) { + bail!( + "a remote graph must be addressed with `--server ` — a positional \ + (or `--uri`) http(s):// URL no longer dispatches to a server" + ); + } + Ok(()) +} + impl GraphClient { /// Resolve the addressing (positional URI / `--target` / `--server`) /// and credential once, then pick the variant by URI scheme — the @@ -78,27 +91,27 @@ impl GraphClient { server: Option<&str>, graph: Option<&str>, uri: Option, - target: Option<&str>, profile: Option<&str>, store: Option<&str>, ) -> Result { // RFC-011: a scope (profile / --store / operator defaults) may stand in - // for omitted addressing. The explicit branch passes server/graph/uri/ - // target straight through, so existing invocations are unchanged. + // for omitted addressing. The explicit branch passes server/graph/uri + // straight through, so existing invocations are unchanged. let scope = crate::scope::resolve_scope( &crate::operator::load_operator_config()?, crate::planes::Capability::Any, - crate::scope::ScopeFlags { profile, store, server, graph, uri, target }, + crate::scope::ScopeFlags { profile, store, server, graph, uri }, )?; - let (server, graph, uri, target) = ( + let (server, graph, uri) = ( scope.server.as_deref(), scope.graph.as_deref(), scope.uri, - scope.target.as_deref(), ); - let uri = apply_server_flag(server, graph, uri, target)?; - let token = resolve_remote_bearer_token(config, uri.as_deref(), target)?; - let uri = crate::helpers::resolve_uri(config, uri, target)?; + let via_server = server.is_some(); + let uri = apply_server_flag(server, graph, uri)?; + let token = resolve_remote_bearer_token(config, uri.as_deref())?; + let uri = crate::helpers::resolve_uri(config, uri)?; + reject_positional_remote(via_server, &uri)?; if is_remote_uri(&uri) { Ok(GraphClient::Remote { http: build_http_client()?, @@ -125,7 +138,6 @@ impl GraphClient { server: Option<&str>, graph: Option<&str>, uri: Option, - target: Option<&str>, cli_as: Option<&str>, profile: Option<&str>, store: Option<&str>, @@ -135,18 +147,28 @@ impl GraphClient { let scope = crate::scope::resolve_scope( &crate::operator::load_operator_config()?, crate::planes::Capability::Any, - crate::scope::ScopeFlags { profile, store, server, graph, uri, target }, + crate::scope::ScopeFlags { profile, store, server, graph, uri }, )?; - let (server, graph, uri, target) = ( + let (server, graph, uri) = ( scope.server.as_deref(), scope.graph.as_deref(), scope.uri, - scope.target.as_deref(), ); - let uri = apply_server_flag(server, graph, uri, target)?; - let token = resolve_remote_bearer_token(config, uri.as_deref(), target)?; - let resolved = resolve_cli_graph(config, uri, target)?; + let via_server = server.is_some(); + let uri = apply_server_flag(server, graph, uri)?; + let token = resolve_remote_bearer_token(config, uri.as_deref())?; + let resolved = resolve_cli_graph(config, uri)?; + reject_positional_remote(via_server, &resolved.uri)?; if resolved.is_remote { + // A served write resolves the actor server-side from the bearer + // token; `--as` cannot set identity here and is rejected. + if cli_as.is_some() { + bail!( + "`--as` is not allowed on a served write — the server resolves the actor \ + from the bearer token. Remove `--as`, or run the write directly against \ + storage with `--store `." + ); + } Ok(GraphClient::Remote { http: build_http_client()?, base_url: resolved.uri, diff --git a/crates/omnigraph-cli/src/helpers.rs b/crates/omnigraph-cli/src/helpers.rs index 5d1cd39..d49d17f 100644 --- a/crates/omnigraph-cli/src/helpers.rs +++ b/crates/omnigraph-cli/src/helpers.rs @@ -245,8 +245,9 @@ pub(crate) fn normalize_policy_graph_uri(uri: &str) -> Result { pub(crate) fn resolve_remote_bearer_token( config: &OmnigraphConfig, explicit_uri: Option<&str>, - explicit_target: Option<&str>, ) -> Result> { + // `--target` is gone; the legacy explicit-target name is always None. + let explicit_target: Option<&str> = None; // The keyed hop (RFC-007 §D4, gh-host model): when the effective remote // URL belongs to an operator-defined server, that server's keyed chain // applies first — OMNIGRAPH_TOKEN_ env, then the 0600 credentials @@ -303,19 +304,26 @@ pub(crate) fn resolve_server_flag( let Some(server) = server else { return Ok(None); }; - let operator_config = operator::load_operator_config()?; - let Some(entry) = operator_config.servers.get(server) else { - let known = operator_config - .servers - .keys() - .map(String::as_str) - .collect::>() - .join(", "); - color_eyre::eyre::bail!( - "unknown server '{server}' — servers defined in the operator config: [{known}] (add it under servers: in ~/.omnigraph/config.yaml)" - ); + // RFC-011 Decision 2: a value containing `://` is a literal base URL + // (bypasses the operator-config registry); otherwise it is a config name. + let base_url = if server.contains("://") { + server.to_string() + } else { + let operator_config = operator::load_operator_config()?; + let Some(entry) = operator_config.servers.get(server) else { + let known = operator_config + .servers + .keys() + .map(String::as_str) + .collect::>() + .join(", "); + color_eyre::eyre::bail!( + "unknown server '{server}' — servers defined in the operator config: [{known}] (add it under servers: in ~/.omnigraph/config.yaml)" + ); + }; + entry.url.clone() }; - let base = entry.url.trim_end_matches('/'); + let base = base_url.trim_end_matches('/'); Ok(Some(match graph { Some(graph) => format!("{base}/graphs/{graph}"), None => base.to_string(), @@ -336,7 +344,7 @@ pub(crate) async fn execute_operator_alias( ) -> Result { let uri = resolve_server_flag(Some(&alias.server), alias.graph.as_deref())? .expect("server name is present"); - let bearer_token = resolve_remote_bearer_token(config, Some(&uri), None)?; + let bearer_token = resolve_remote_bearer_token(config, Some(&uri))?; let mut params = serde_json::Map::new(); for (key, value) in &alias.params { @@ -379,14 +387,13 @@ pub(crate) fn apply_server_flag( server: Option<&str>, graph: Option<&str>, uri: Option, - target: Option<&str>, ) -> Result> { if server.is_none() { return Ok(uri); } - if uri.is_some() || target.is_some() { + if uri.is_some() { color_eyre::eyre::bail!( - "--server is exclusive with a positional URI and --target — pick one way to address the graph" + "--server is exclusive with a positional URI — pick one way to address the graph" ); } resolve_server_flag(server, graph) @@ -448,28 +455,23 @@ pub(crate) async fn remote_json( Ok(serde_json::from_str(&text)?) } -pub(crate) fn resolve_uri( - config: &OmnigraphConfig, - cli_uri: Option, - cli_target: Option<&str>, -) -> Result { - config.resolve_target_uri(cli_uri, cli_target, config.cli_graph_name()) +pub(crate) fn resolve_uri(config: &OmnigraphConfig, cli_uri: Option) -> Result { + // `--target` is gone; the second arg (the legacy explicit-target name) is + // always None. A bare command still falls back to `cli.graph` (the third arg). + config.resolve_target_uri(cli_uri, None, config.cli_graph_name()) } pub(crate) fn resolve_cli_graph( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, ) -> Result { let selected = if cli_uri.is_some() { None } else { - cli_target - .map(str::to_string) - .or_else(|| config.cli_graph_name().map(str::to_string)) + config.cli_graph_name().map(str::to_string) }; config.resolve_graph_selection(selected.as_deref())?; - let uri = resolve_uri(config, cli_uri, cli_target)?; + let uri = resolve_uri(config, cli_uri)?; let normalized_uri = normalize_policy_graph_uri(&uri)?; let graph_id = graph_resource_id_for_selection(selected.as_deref(), &normalized_uri); Ok(ResolvedCliGraph { @@ -484,10 +486,9 @@ pub(crate) fn resolve_cli_graph( pub(crate) fn resolve_local_graph( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, operation: &str, ) -> Result { - let graph = resolve_cli_graph(config, cli_uri, cli_target)?; + let graph = resolve_cli_graph(config, cli_uri)?; if graph.is_remote { bail!( "`{}` is a direct (storage-native) command and needs direct storage \ @@ -533,29 +534,27 @@ pub(crate) fn parse_duration_arg(s: &str) -> Result { pub(crate) fn resolve_local_uri( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, operation: &str, ) -> Result { - Ok(resolve_local_graph(config, cli_uri, cli_target, operation)?.uri) + Ok(resolve_local_graph(config, cli_uri, operation)?.uri) } -/// Resolve a storage-plane verb's target to a direct storage URI (RFC-010 +/// Resolve a storage-plane verb's address to a direct storage URI (RFC-010 /// Slice 3). `--cluster --cluster-graph ` resolves the graph's /// storage URI from the **served cluster state** (the truth a `--cluster` -/// server serves); otherwise the ordinary positional-URI / `--target` path. -/// clap enforces both-or-neither and exclusion with `uri`/`--target`, so the -/// mismatched arm is defensive. +/// server serves); otherwise the ordinary positional-URI path. +/// clap enforces both-or-neither and exclusion with `uri`, so the mismatched +/// arm is defensive. pub(crate) async fn resolve_storage_uri( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, cluster: Option<&str>, cluster_graph: Option<&str>, operation: &str, ) -> Result { match (cluster, cluster_graph) { (Some(cluster), Some(graph_id)) => resolve_cluster_graph_uri(cluster, graph_id).await, - (None, None) => resolve_local_uri(config, cli_uri, cli_target, operation), + (None, None) => resolve_local_uri(config, cli_uri, operation), _ => bail!("--cluster and --cluster-graph must be given together"), } } @@ -786,7 +785,6 @@ pub(crate) fn query_params_from_json( pub(crate) async fn execute_query_lint( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, schema_path: Option<&PathBuf>, query_path: &PathBuf, ) -> Result { @@ -808,13 +806,12 @@ pub(crate) async fn execute_query_lint( )); } - let has_graph_target = - cli_uri.is_some() || cli_target.is_some() || config.cli_graph_name().is_some(); + let has_graph_target = cli_uri.is_some() || config.cli_graph_name().is_some(); if !has_graph_target { bail!("lint requires --schema or a resolvable graph target"); } - let uri = resolve_local_uri(config, cli_uri, cli_target, "lint")?; + let uri = resolve_local_uri(config, cli_uri, "lint")?; let db = Omnigraph::open(&uri).await?; Ok(lint_query_file( &db.catalog(), @@ -827,10 +824,9 @@ pub(crate) async fn execute_query_lint( pub(crate) fn resolve_selected_graph( config: &OmnigraphConfig, cli_uri: Option, - cli_target: Option<&str>, operation: &str, ) -> Result<(String, Option)> { - let graph = resolve_local_graph(config, cli_uri, cli_target, operation)?; + let graph = resolve_local_graph(config, cli_uri, operation)?; Ok((graph.uri, graph.selected)) } @@ -860,11 +856,8 @@ pub(crate) fn graph_query_registry_names(config: &OmnigraphConfig) -> Vec<&str> pub(crate) fn resolve_registry_selection_for_list( config: &OmnigraphConfig, - target: Option<&str>, ) -> Result> { - let selected = target - .map(str::to_string) - .or_else(|| config.cli_graph_name().map(str::to_string)); + let selected = config.cli_graph_name().map(str::to_string); if let Some(name) = selected.as_deref() { config.resolve_graph_selection(Some(name))?; return Ok(selected); @@ -880,10 +873,9 @@ pub(crate) fn resolve_registry_selection_for_list( } bail!( - "stored-query registries are configured for graph{} {} but no graph was selected. Pass `--target {}` or set `cli.graph`.", + "stored-query registries are configured for graph{} {} but no graph was selected. Pass a positional URI or set `cli.graph`.", if graph_names.len() == 1 { "" } else { "s" }, graph_names.join(", "), - graph_names[0], ) } @@ -903,15 +895,12 @@ pub(crate) fn validate_registry_for_catalog( pub(crate) async fn execute_queries_validate( uri: Option, - target: Option, config_path: Option<&PathBuf>, json: bool, ) -> Result<()> { let config = load_cli_config(config_path)?; - // One selection drives both the schema URI and the registry, so a - // positional URI and a `--target` can't validate different graphs. - let (uri, selected) = - resolve_selected_graph(&config, uri, target.as_deref(), "queries validate")?; + // One selection drives both the schema URI and the registry. + let (uri, selected) = resolve_selected_graph(&config, uri, "queries validate")?; let registry = load_registry_or_report(&config, selected.as_deref())?; let db = Omnigraph::open(&uri).await?; let report = check(®istry, &db.catalog()); @@ -961,13 +950,9 @@ pub(crate) async fn execute_queries_validate( Ok(()) } -pub(crate) fn execute_queries_list( - target: Option, - config_path: Option<&PathBuf>, - json: bool, -) -> Result<()> { +pub(crate) fn execute_queries_list(config_path: Option<&PathBuf>, json: bool) -> Result<()> { let config = load_cli_config(config_path)?; - let selected = resolve_registry_selection_for_list(&config, target.as_deref())?; + let selected = resolve_registry_selection_for_list(&config)?; let registry = load_registry_or_report(&config, selected.as_deref())?; let output = QueriesListOutput { @@ -1090,6 +1075,22 @@ pub(crate) fn rewrite_deprecated_argv(args: Vec) -> Vec { mod tests { use super::*; + // RFC-011 Decision 2: `--server` accepts a literal URL (value with `://`), + // bypassing the operator-config registry — so no config / OMNIGRAPH_HOME is + // read on this path (hermetic). + #[test] + fn server_flag_accepts_a_literal_url() { + assert_eq!( + resolve_server_flag(Some("https://graph.example.com"), None).unwrap(), + Some("https://graph.example.com".to_string()) + ); + // trailing slash trimmed; `--graph` appends the multi-graph path. + assert_eq!( + resolve_server_flag(Some("https://graph.example.com/"), Some("knowledge")).unwrap(), + Some("https://graph.example.com/graphs/knowledge".to_string()) + ); + } + // `branch delete` interpolates the branch into the URL path. The composed // path must be exactly `/branches/` with no empty `//` // segment — an empty segment misses the diff --git a/crates/omnigraph-cli/src/main.rs b/crates/omnigraph-cli/src/main.rs index 988fab9..606db96 100644 --- a/crates/omnigraph-cli/src/main.rs +++ b/crates/omnigraph-cli/src/main.rs @@ -170,7 +170,6 @@ async fn main() -> Result<()> { } Command::Load { uri, - target, config, data, branch, @@ -184,7 +183,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -201,7 +199,6 @@ async fn main() -> Result<()> { } Command::Ingest { uri, - target, config, data, branch, @@ -220,7 +217,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -239,7 +235,6 @@ async fn main() -> Result<()> { Command::Branch { command } => match command { BranchCommand::Create { uri, - target, config, from, name, @@ -251,7 +246,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -266,7 +260,6 @@ async fn main() -> Result<()> { } BranchCommand::List { uri, - target, config, json, } => { @@ -276,7 +269,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -291,7 +283,6 @@ async fn main() -> Result<()> { } BranchCommand::Delete { uri, - target, config, name, json, @@ -302,7 +293,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -316,7 +306,6 @@ async fn main() -> Result<()> { } BranchCommand::Merge { uri, - target, config, source, into, @@ -328,7 +317,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -350,7 +338,6 @@ async fn main() -> Result<()> { Command::Commit { command } => match command { CommitCommand::List { uri, - target, config, branch, json, @@ -361,7 +348,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -374,7 +360,6 @@ async fn main() -> Result<()> { } CommitCommand::Show { uri, - target, config, commit_id, json, @@ -385,7 +370,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -400,14 +384,13 @@ async fn main() -> Result<()> { Command::Schema { command } => match command { SchemaCommand::Plan { uri, - target, config, schema, json, allow_data_loss, } => { let config = load_cli_config(config.as_ref())?; - let uri = resolve_local_uri(&config, uri, target.as_deref(), "schema plan")?; + let uri = resolve_local_uri(&config, uri, "schema plan")?; let schema_source = fs::read_to_string(&schema)?; let db = Omnigraph::open(&uri).await?; let plan = db @@ -430,7 +413,6 @@ async fn main() -> Result<()> { } SchemaCommand::Apply { uri, - target, config, schema, json, @@ -442,7 +424,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -475,7 +456,6 @@ async fn main() -> Result<()> { } SchemaCommand::Show { uri, - target, config, json, } => { @@ -485,7 +465,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -499,7 +478,6 @@ async fn main() -> Result<()> { }, Command::Lint { uri, - target, config, query, schema, @@ -507,30 +485,27 @@ async fn main() -> Result<()> { } => { let config = load_cli_config(config.as_ref())?; let output = - execute_query_lint(&config, uri, target.as_deref(), schema.as_ref(), &query) + execute_query_lint(&config, uri, schema.as_ref(), &query) .await?; finish_query_lint(&output, json)?; } Command::Queries { command } => match command { QueriesCommand::Validate { uri, - target, config, json, } => { - execute_queries_validate(uri, target, config.as_ref(), json).await?; + execute_queries_validate(uri, config.as_ref(), json).await?; } QueriesCommand::List { - target, config, json, } => { - execute_queries_list(target, config.as_ref(), json)?; + execute_queries_list(config.as_ref(), json)?; } }, Command::Snapshot { uri, - target, config, branch, json, @@ -541,7 +516,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -555,7 +529,6 @@ async fn main() -> Result<()> { } Command::Export { uri, - target, config, branch, jsonl, @@ -568,7 +541,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -586,7 +558,6 @@ async fn main() -> Result<()> { Command::Query { uri, legacy_uri, - target, config, alias, query, @@ -644,23 +615,24 @@ async fn main() -> Result<()> { let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Read)?; let alias_name = alias.as_ref().map(|(name, _)| *name); let alias_config = alias.as_ref().map(|(_, alias)| *alias); - let target_available = target.is_some() - || alias_config - .and_then(|alias| alias.graph.as_deref()) - .is_some() - || config.cli_graph_name().is_some(); + let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref()); + let target_available = alias_graph.is_some() || config.cli_graph_name().is_some(); let (legacy_uri, alias_args) = normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args); - let uri = uri.or(legacy_uri); - let target_name = target - .as_deref() - .or_else(|| alias_config.and_then(|alias| alias.graph.as_deref())); + // `--target` is gone; resolve an alias's legacy `graph` name to its + // URI (a positional URI still wins). + let uri = match uri.or(legacy_uri) { + Some(uri) => Some(uri), + None => match alias_graph { + Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?), + None => None, + }, + }; let client = client::GraphClient::resolve( &config, cli.server.as_deref(), cli.graph.as_deref(), uri, - target_name, cli.profile.as_deref(), cli.store.as_deref(), )?; @@ -704,7 +676,6 @@ async fn main() -> Result<()> { Command::Mutate { uri, legacy_uri, - target, config, alias, query, @@ -723,23 +694,24 @@ async fn main() -> Result<()> { let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Change)?; let alias_name = alias.as_ref().map(|(name, _)| *name); let alias_config = alias.as_ref().map(|(_, alias)| *alias); - let target_available = target.is_some() - || alias_config - .and_then(|alias| alias.graph.as_deref()) - .is_some() - || config.cli_graph_name().is_some(); + let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref()); + let target_available = alias_graph.is_some() || config.cli_graph_name().is_some(); let (legacy_uri, alias_args) = normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args); - let uri = uri.or(legacy_uri); - let target_name = target - .as_deref() - .or_else(|| alias_config.and_then(|alias| alias.graph.as_deref())); + // `--target` is gone; resolve an alias's legacy `graph` name to its + // URI (a positional URI still wins). + let uri = match uri.or(legacy_uri) { + Some(uri) => Some(uri), + None => match alias_graph { + Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?), + None => None, + }, + }; let client = client::GraphClient::resolve_with_policy( &config, cli.server.as_deref(), cli.graph.as_deref(), uri, - target_name, cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), @@ -820,18 +792,16 @@ async fn main() -> Result<()> { }, Command::Optimize { uri, - target, config, cluster, cluster_graph, json, } => { let config = load_cli_config(config.as_ref())?; - let uri = if uri.is_some() || target.is_some() || cluster.is_some() { + let uri = if uri.is_some() || cluster.is_some() { resolve_storage_uri( &config, uri, - target.as_deref(), cluster.as_deref(), cluster_graph.as_deref(), "optimize", @@ -849,13 +819,11 @@ async fn main() -> Result<()> { server: None, graph: cli.graph.as_deref(), uri: None, - target: None, }, )?; resolve_storage_uri( &config, scope.uri, - scope.target.as_deref(), scope.cluster.as_deref(), scope.cluster_graph.as_deref(), "optimize", @@ -896,7 +864,6 @@ async fn main() -> Result<()> { } Command::Repair { uri, - target, config, cluster, cluster_graph, @@ -905,11 +872,10 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; - let uri = if uri.is_some() || target.is_some() || cluster.is_some() { + let uri = if uri.is_some() || cluster.is_some() { resolve_storage_uri( &config, uri, - target.as_deref(), cluster.as_deref(), cluster_graph.as_deref(), "repair", @@ -926,13 +892,11 @@ async fn main() -> Result<()> { server: None, graph: cli.graph.as_deref(), uri: None, - target: None, }, )?; resolve_storage_uri( &config, scope.uri, - scope.target.as_deref(), scope.cluster.as_deref(), scope.cluster_graph.as_deref(), "repair", @@ -1014,7 +978,6 @@ async fn main() -> Result<()> { } Command::Cleanup { uri, - target, config, cluster, cluster_graph, @@ -1024,11 +987,10 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; - let uri = if uri.is_some() || target.is_some() || cluster.is_some() { + let uri = if uri.is_some() || cluster.is_some() { resolve_storage_uri( &config, uri, - target.as_deref(), cluster.as_deref(), cluster_graph.as_deref(), "cleanup", @@ -1045,13 +1007,11 @@ async fn main() -> Result<()> { server: None, graph: cli.graph.as_deref(), uri: None, - target: None, }, )?; resolve_storage_uri( &config, scope.uri, - scope.target.as_deref(), scope.cluster.as_deref(), scope.cluster_graph.as_deref(), "cleanup", @@ -1182,7 +1142,6 @@ async fn main() -> Result<()> { Command::Graphs { command } => match command { GraphsCommand::List { uri, - target, config, json, } => { @@ -1192,7 +1151,6 @@ async fn main() -> Result<()> { cli.server.as_deref(), cli.graph.as_deref(), uri, - target.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; diff --git a/crates/omnigraph-cli/src/main_tests.rs b/crates/omnigraph-cli/src/main_tests.rs index 8380c36..2e1db5c 100644 --- a/crates/omnigraph-cli/src/main_tests.rs +++ b/crates/omnigraph-cli/src/main_tests.rs @@ -209,13 +209,7 @@ cli: let config = load_config(Some(&config_path)).unwrap(); assert_eq!( - resolve_remote_bearer_token(&config, None, Some("demo")) - .unwrap() - .as_deref(), - Some("scoped-token") - ); - assert_eq!( - resolve_remote_bearer_token(&config, Some("https://override.example.com"), None) + resolve_remote_bearer_token(&config, Some("https://override.example.com")) .unwrap() .as_deref(), Some("global-token") @@ -369,12 +363,16 @@ graphs: uri: s3://bucket/prod-graph/ policy: file: ./prod-policy.yaml +cli: + graph: prod "#, ) .unwrap(); let config = load_config(Some(&config_path)).unwrap(); - let graph = resolve_cli_graph(&config, None, Some("prod")).unwrap(); + // `--target` is removed; the `cli.graph` default drives the same + // graph-key (not project name / URI) selection. + let graph = resolve_cli_graph(&config, None).unwrap(); assert_eq!(graph.selected(), Some("prod")); assert_eq!(graph.graph_id, "prod"); assert_eq!(graph.uri, "s3://bucket/prod-graph/"); @@ -405,7 +403,6 @@ cli: let local_graph = resolve_cli_graph( &config, Some(format!("file://{}", local_graph_path.display())), - None, ) .unwrap(); assert_eq!(local_graph.selected(), None); @@ -418,7 +415,6 @@ cli: let s3_graph = resolve_cli_graph( &config, Some("s3://bucket/anonymous-graph/".to_string()), - None, ) .unwrap(); assert_eq!(s3_graph.selected(), None); diff --git a/crates/omnigraph-cli/src/migrate.rs b/crates/omnigraph-cli/src/migrate.rs index 3891061..7410381 100644 --- a/crates/omnigraph-cli/src/migrate.rs +++ b/crates/omnigraph-cli/src/migrate.rs @@ -66,7 +66,7 @@ pub(crate) fn build_report(config: &OmnigraphConfig, source: &Path) -> MigrateRe if config.cli.graph.is_some() { dropped.push(DroppedKey { key: "cli.graph".into(), - reason: "no operator default-target yet — address graphs explicitly via --target/--server (RFC-002 locator territory)".into(), + reason: "address graphs explicitly via --store/--server, or set defaults.default_graph in the operator config".into(), }); } if config.cli.branch.is_some() { diff --git a/crates/omnigraph-cli/src/planes.rs b/crates/omnigraph-cli/src/planes.rs index dae6440..c289daa 100644 --- a/crates/omnigraph-cli/src/planes.rs +++ b/crates/omnigraph-cli/src/planes.rs @@ -192,11 +192,9 @@ pub(crate) fn guard_addressing(cli: &Cli) -> Result<()> { } let label = command_label(&cli.command); let how = match capability { - // `init` is the one direct verb with no `--target` today (it takes a - // required positional URI), so its remediation drops the `--target` half. Capability::Direct => match cli.command { Command::Init { .. } => "Pass a storage URI.", - _ => "Use --target , a storage URI, or --cluster --cluster-graph .", + _ => "Pass a storage URI, or --cluster --cluster-graph .", }, Capability::Control => "It operates on a cluster (pass --config ).", Capability::Local => "It does not address a graph.", @@ -234,6 +232,9 @@ mod tests { let cap = |args: &[&str]| { command_capability(&Cli::try_parse_from(args).unwrap().command) }; + // The one Data→Served refinement — if the `graphs` guard were deleted, + // every other assertion here would still pass. + assert_eq!(cap(&["omnigraph", "graphs", "list"]), Capability::Served); assert_eq!(cap(&["omnigraph", "optimize", "graph.omni"]), Capability::Direct); assert_eq!(cap(&["omnigraph", "schema", "plan", "--schema", "s.pg", "graph.omni"]), Capability::Direct); assert_eq!(cap(&["omnigraph", "cluster", "status", "--config", "."]), Capability::Control); diff --git a/crates/omnigraph-cli/src/scope.rs b/crates/omnigraph-cli/src/scope.rs index 692ff0a..4349231 100644 --- a/crates/omnigraph-cli/src/scope.rs +++ b/crates/omnigraph-cli/src/scope.rs @@ -32,24 +32,22 @@ pub(crate) struct ResolvedScope { pub(crate) server: Option, pub(crate) graph: Option, pub(crate) uri: Option, - pub(crate) target: Option, pub(crate) cluster: Option, pub(crate) cluster_graph: Option, } /// The raw addressing inputs for one command: the global scope flags plus the -/// command's own positional/`--target` address. +/// command's own positional URI. pub(crate) struct ScopeFlags<'a> { pub(crate) profile: Option<&'a str>, pub(crate) store: Option<&'a str>, pub(crate) server: Option<&'a str>, pub(crate) graph: Option<&'a str>, pub(crate) uri: Option, - pub(crate) target: Option<&'a str>, } /// Resolve the scope for a command with `capability`. Precedence (RFC-011): -/// 1. explicit legacy/primitive address (`uri`/`target`/`--server`/`--store`) → passthrough; +/// 1. explicit primitive address (`uri`/`--server`/`--store`) → passthrough; /// 2. `--profile` / `OMNIGRAPH_PROFILE`; /// 3. flat `defaults.server` + `defaults.default_graph`; /// 4. nothing — downstream behaves as today. @@ -58,15 +56,21 @@ pub(crate) fn resolve_scope( capability: Capability, flags: ScopeFlags<'_>, ) -> Result { + // `--store` is its own way to address a graph; combining it with a positional + // URI or `--server` is a contradiction, not a silent precedence. + if flags.store.is_some() && (flags.uri.is_some() || flags.server.is_some()) { + bail!( + "--store is exclusive with a positional URI and --server — pick one way to \ + address the graph" + ); + } // 1. Any explicit address wins; reproduce today's behavior untouched. // `--store` is an explicit store URI — fold it into `uri`. - if flags.uri.is_some() || flags.target.is_some() || flags.server.is_some() || flags.store.is_some() - { + if flags.uri.is_some() || flags.server.is_some() || flags.store.is_some() { return Ok(ResolvedScope { server: flags.server.map(str::to_string), graph: flags.graph.map(str::to_string), uri: flags.store.map(str::to_string).or(flags.uri), - target: flags.target.map(str::to_string), ..Default::default() }); } @@ -190,7 +194,6 @@ mod tests { server: None, graph: None, uri: None, - target: None, } } @@ -226,6 +229,26 @@ mod tests { assert_eq!(scope.uri.as_deref(), Some("s3://b/g.omni")); } + #[test] + fn store_is_exclusive_with_positional_uri_and_server() { + let op = OperatorConfig::default(); + for flags in [ + ScopeFlags { + store: Some("s3://b/g.omni"), + uri: Some("file://other.omni".into()), + ..flags() + }, + ScopeFlags { + store: Some("s3://b/g.omni"), + server: Some("prod"), + ..flags() + }, + ] { + let err = resolve_scope(&op, Capability::Any, flags).unwrap_err().to_string(); + assert!(err.contains("--store is exclusive"), "{err}"); + } + } + #[test] fn flat_default_server_drives_data_verbs() { let op = cfg("defaults:\n server: prod\n default_graph: knowledge\nservers:\n prod:\n url: https://x\n"); diff --git a/crates/omnigraph-cli/tests/cli_data.rs b/crates/omnigraph-cli/tests/cli_data.rs index f7fbc7a..fc5db0a 100644 --- a/crates/omnigraph-cli/tests/cli_data.rs +++ b/crates/omnigraph-cli/tests/cli_data.rs @@ -166,7 +166,7 @@ fn optimize_with_server_flag_errors_wrong_plane() { assert!( stderr.contains("`optimize` is a direct (storage-native) command") && stderr.contains("--server/--graph address a served graph and do not apply") - && stderr.contains("Use --target , a storage URI, or --cluster --cluster-graph ."), + && stderr.contains("Pass a storage URI, or --cluster --cluster-graph ."), "wrong-capability guard message not found; got: {stderr}" ); } @@ -1279,6 +1279,48 @@ fn read_supports_inline_query_string() { assert_eq!(payload["rows"][0]["p.name"], "Alice"); } +#[test] +fn positional_http_uri_on_a_data_verb_is_rejected() { + // RFC-011: a positional/`--uri` http(s):// URL no longer dispatches to a + // remote server — that requires `--server `. + let output = output_failure( + cli() + .arg("query") + .arg("http://127.0.0.1:1") + .arg("-e") + .arg("query q() { match { $p: Person { } } return { $p } }"), + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("must be addressed with `--server `"), + "expected positional-remote rejection; got: {stderr}" + ); +} + +#[test] +fn as_on_a_served_write_is_rejected() { + // RFC-011: a served write resolves the actor from the bearer token, so --as + // cannot set identity. It errors while building the remote client — before + // any HTTP call, so no server is needed. + let output = output_failure( + cli() + .arg("mutate") + .arg("--server") + .arg("http://127.0.0.1:1") + .arg("--as") + .arg("act-nope") + .arg("-e") + .arg("query add($name: String) { insert Person { name: $name } }") + .arg("--params") + .arg(r#"{"name":"X"}"#), + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("`--as` is not allowed on a served write"), + "expected --as-served rejection; got: {stderr}" + ); +} + #[test] fn change_supports_inline_query_string() { let temp = tempdir().unwrap(); diff --git a/crates/omnigraph-cli/tests/cli_queries.rs b/crates/omnigraph-cli/tests/cli_queries.rs index 8a1e553..2f2ff00 100644 --- a/crates/omnigraph-cli/tests/cli_queries.rs +++ b/crates/omnigraph-cli/tests/cli_queries.rs @@ -323,7 +323,7 @@ fn queries_list_requires_graph_selection_for_per_graph_only_registries() { ); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("local") && stderr.contains("--target local"), + stderr.contains("local") && stderr.contains("set `cli.graph`"), "error must name the graph and give a concrete selection hint; stderr:\n{stderr}" ); } @@ -357,12 +357,12 @@ fn queries_list_without_graph_selection_lists_top_level_registry() { } #[test] -fn queries_list_unknown_target_errors() { +fn queries_list_unknown_cli_graph_errors() { // `queries list` opens no graph URI, so unknown-graph validation can't ride // along on URI resolution the way it does for every other command. An - // unknown `--target` must still error (naming the graph) instead of - // silently falling back to the top-level registry and showing the wrong - // (or empty) catalog. + // unknown `cli.graph` selection must still error (naming the graph) instead + // of silently falling back to the top-level registry and showing the wrong + // (or empty) catalog. (`--target` was removed; `cli.graph` drives selection.) let graph = SystemGraph::loaded(); graph.write_query( "find_person.gq", @@ -370,21 +370,12 @@ fn queries_list_unknown_target_errors() { ); let config = graph.write_config( "omnigraph.yaml", - &queries_test_config( - &graph.path().to_string_lossy(), - "find_person", - "find_person.gq", + &format!( + "graphs:\n local:\n uri: '{}'\n queries:\n find_person:\n file: ./find_person.gq\ncli:\n graph: nonexistent\npolicy: {{}}\n", + graph.path().to_string_lossy().replace('\'', "''"), ), ); - let output = output_failure( - cli() - .arg("queries") - .arg("list") - .arg("--target") - .arg("nonexistent") - .arg("--config") - .arg(&config), - ); + let output = output_failure(cli().arg("queries").arg("list").arg("--config").arg(&config)); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("nonexistent"), diff --git a/crates/omnigraph-cli/tests/cli_schema_config.rs b/crates/omnigraph-cli/tests/cli_schema_config.rs index 9751517..0b6eca9 100644 --- a/crates/omnigraph-cli/tests/cli_schema_config.rs +++ b/crates/omnigraph-cli/tests/cli_schema_config.rs @@ -121,7 +121,7 @@ fn schema_plan_with_server_flag_errors_wrong_plane() { let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("`schema plan` is a direct (storage-native) command") - && stderr.contains("Use --target , a storage URI, or --cluster --cluster-graph ."), + && stderr.contains("Pass a storage URI, or --cluster --cluster-graph ."), "schema plan wrong-capability message not found; got: {stderr}" ); } diff --git a/crates/omnigraph-cli/tests/support/mod.rs b/crates/omnigraph-cli/tests/support/mod.rs index b36288c..c19d6a6 100644 --- a/crates/omnigraph-cli/tests/support/mod.rs +++ b/crates/omnigraph-cli/tests/support/mod.rs @@ -831,8 +831,18 @@ pub fn run_both_with_config( server_url: &str, args: &[&str], ) -> (std::process::Output, std::process::Output) { + // Address both arms with GLOBAL flags (`--store` / `--server`) appended after + // the verb + its args, so the address is placed correctly regardless of + // subcommand nesting (a positional graph only works for top-level verbs; + // `schema show ` etc. need the global flag). Local = embedded store, + // remote = served. let mut local = cli(); - local.arg(args[0]).arg(local_graph).args(&args[1..]).arg("--as").arg(PARITY_ACTOR); + local + .args(args) + .arg("--store") + .arg(local_graph) + .arg("--as") + .arg(PARITY_ACTOR); if let Some(config) = local_config { local.arg("--config").arg(config); } @@ -841,9 +851,9 @@ pub fn run_both_with_config( let mut remote = cli(); remote .env("OMNIGRAPH_BEARER_TOKEN", PARITY_TOKEN) - .arg(args[0]) - .arg(server_url) - .args(&args[1..]); + .args(args) + .arg("--server") + .arg(server_url); let remote_out = remote.output().unwrap(); (local_out, remote_out) } diff --git a/crates/omnigraph-cli/tests/system_local.rs b/crates/omnigraph-cli/tests/system_local.rs index 28ed7a3..b6a87f1 100644 --- a/crates/omnigraph-cli/tests/system_local.rs +++ b/crates/omnigraph-cli/tests/system_local.rs @@ -2339,6 +2339,7 @@ fn local_cli_keyed_credentials_authenticate_url_matched_server() { } command .arg("read") + .arg("--server") .arg(&server.base_url) .arg("--query") .arg(fixture("test.gq")) diff --git a/docs/user/cli/index.md b/docs/user/cli/index.md index 6813744..6df606c 100644 --- a/docs/user/cli/index.md +++ b/docs/user/cli/index.md @@ -60,14 +60,16 @@ Read through the HTTP API: ```bash omnigraph query \ - --target http://127.0.0.1:8080 \ + --server http://127.0.0.1:8080 \ --query queries.gq \ --name get_person \ --params '{"name":"Alice"}' ``` -If the server requires auth, set `OMNIGRAPH_SERVER_BEARER_TOKEN` on the server -and configure the matching `bearer_token_env` in `omnigraph.yaml`. +A server is addressed with `--server` (a name from `~/.omnigraph/config.yaml` or a +literal URL); a positional `http(s)://` URI is rejected. If the server requires +auth, set its bearer token and `omnigraph login ` (or +`OMNIGRAPH_BEARER_TOKEN`). ## Multi-graph servers (v0.6.0+) diff --git a/docs/user/cli/reference.md b/docs/user/cli/reference.md index 15ce953..3da502a 100644 --- a/docs/user/cli/reference.md +++ b/docs/user/cli/reference.md @@ -2,7 +2,7 @@ A reference for the `omnigraph` binary's command surface and `omnigraph.yaml` schema. For a quick-start guide, see [cli.md](index.md). -Top-level command families and subcommands. Graph-targeting commands accept a positional `URI`, `--uri`, a `--target ` resolved against `omnigraph.yaml`, `--server ` (an operator-defined server from `~/.omnigraph/config.yaml`, optionally with `--graph ` for multi-graph servers; exclusive with the other forms), `--store ` (a single graph's storage directly), or `--profile ` / `$OMNIGRAPH_PROFILE` (a named scope bundle; see [Scopes & profiles](#scopes--profiles-rfc-011)); `cluster` commands use `--config `. +Top-level command families and subcommands. Graph-targeting commands accept a positional `file://`/`s3://` URI, `--server ` (an operator-defined server from `~/.omnigraph/config.yaml` by name, or a literal `http(s)://` URL, optionally with `--graph ` for multi-graph servers; exclusive with a positional URI), `--store ` (a single graph's storage directly), or `--profile ` / `$OMNIGRAPH_PROFILE` (a named scope bundle; see [Scopes & profiles](#scopes--profiles-rfc-011)); `cluster` commands use `--config `. A remote server is addressed only with `--server` — a positional `http(s)://` URI is rejected. ## Top-level commands @@ -32,19 +32,20 @@ Top-level command families and subcommands. Graph-targeting commands accept a po Every command declares the **capability** it needs — what it requires to reach a graph — which determines the addressing flags that apply: -- **`any`** — `query`, `mutate`, `load`, `ingest`, `branch *`, `snapshot`, `export`, `commit *`, `schema show`, `schema apply`. Run against a graph **served (via a server) or embedded (direct against a store)**: accept a positional `URI` / `--target` / `--server` (+ `--graph` for multi-graph servers) / `--store` / `--profile`. +- **`any`** — `query`, `mutate`, `load`, `ingest`, `branch *`, `snapshot`, `export`, `commit *`, `schema show`, `schema apply`. Run against a graph **served (via a server) or embedded (direct against a store)**: accept a positional `file://`/`s3://` URI, `--server ` (+ `--graph ` for multi-graph servers), `--store `, or `--profile `. A remote server is addressed with `--server` — a positional `http(s)://` URI does **not** dispatch to one. - **`served`** — `graphs list`. Requires a server (accepts `--server` / `--profile`). -- **`direct`** — `init`, `optimize`, `repair`, `cleanup`, `schema plan`, `queries validate`, `lint`. Need **direct storage access** (`file://` / `s3://`), never through a server. They accept a positional `URI` or `--target`, but **not** `--server` / `--graph`, and a `--target` that resolves to a remote (`http(s)://`) server is rejected. (`init` takes only a positional `URI` today — no `--target`.) `optimize` / `repair` / `cleanup` also accept **`--cluster --cluster-graph `**, which resolves the graph's storage URI from the served cluster state (so you needn't know the `/graphs/.omni` layout). +- **`direct`** — `init`, `optimize`, `repair`, `cleanup`, `schema plan`, `queries validate`, `lint`. Need **direct storage access** (`file://` / `s3://`), never through a server. They accept a positional `URI`, but **not** `--server` / `--graph`, and a remote (`http(s)://`) URI is rejected. `optimize` / `repair` / `cleanup` also accept **`--cluster --cluster-graph `**, which resolves the graph's storage URI from the served cluster state (so you needn't know the `/graphs/.omni` layout). - **`control`** — `cluster *`. Operates on a cluster directory via `--config `. - **`local`** — `policy *`, `embed`, `login`, `logout`, `config`, `version`, `queries list`. Address no graph. These restrictions are enforced and reported, not silent: -- A served-graph flag (`--server` / `--graph`) on a verb that doesn't reach a graph through a server fails loudly, e.g.: ``optimize is a direct (storage-native) command; --server/--graph address a served graph and do not apply. Use --target , a storage URI, or --cluster --cluster-graph .`` -- A `direct` verb pointed at a remote target fails loudly, e.g.: ``optimize is a direct (storage-native) command and needs direct storage access; the resolved target is a remote server (https://…). Pass the graph's file:// or s3:// URI.`` +- A served-graph flag (`--server` / `--graph`) on a verb that doesn't reach a graph through a server fails loudly, e.g.: ``optimize is a direct (storage-native) command; --server/--graph address a served graph and do not apply. Pass a storage URI, or --cluster --cluster-graph .`` +- A `direct` verb pointed at a remote URI fails loudly, e.g.: ``optimize is a direct (storage-native) command and needs direct storage access; the resolved target is a remote server (https://…). Pass the graph's file:// or s3:// URI.`` +- A data verb pointed at a positional `http(s)://` URI fails loudly: ``a remote graph must be addressed with --server — a positional (or --uri) http(s):// URL no longer dispatches to a server.`` - `init` into an **established cluster's** storage layout (`/graphs/.omni` where `` holds `__cluster/state.json`) is refused — graphs in a cluster are created by `cluster apply` (which records ledger / recovery / approvals), not `init`. -To maintain a server-backed graph, run the `direct` verbs from a host with storage access against the graph's storage URI (`--target`, or `--cluster … --cluster-graph …`), out-of-band from the serving process — there are no server routes for `optimize` / `repair` / `cleanup` by design. +To maintain a server-backed graph, run the `direct` verbs from a host with storage access against the graph's storage URI (a positional URI, or `--cluster … --cluster-graph …`), out-of-band from the serving process — there are no server routes for `optimize` / `repair` / `cleanup` by design. `omnigraph --help` lists commands with a **capability legend** at the bottom (any / served / direct / control / local). @@ -92,7 +93,7 @@ newer CLI works on an older one). `$OMNIGRAPH_CONFIG=` stands in for A command resolves a **scope** — a server, a cluster, or a store — then selects a graph in it; the served-vs-direct access path is derived from the scope, not toggled. The scope comes from one of (highest precedence first): an explicit -address (a positional URI, `--target`, `--server`, or `--store `); a named +address (a positional URI, `--server`, or `--store `); a named `--profile ` (or `$OMNIGRAPH_PROFILE`); or the flat `defaults.server` + `defaults.default_graph`. A **profile** binds exactly one of `server` / `cluster` / `store` plus an optional default graph — config data, not state: every command @@ -105,9 +106,9 @@ resolves its scope fresh, there is no sticky "current" mode. - A `server`-bound scope on a maintenance verb, or a `cluster`-bound scope on a data verb, is rejected with a message pointing at the right addressing. -This model **coexists** with the legacy addressing (`--uri` / `--target` / -`--cluster-graph` / `omnigraph.yaml`) — nothing is removed yet; an explicit legacy -address always wins. +`--target` and the positional-`http(s)://`→remote dispatch have been **removed**; +the remaining legacy surfaces (`--cluster-graph`, `omnigraph.yaml`'s `cli.graph` +default) still work and an explicit address always wins. #### Credentials keyed by server name diff --git a/docs/user/clusters/config.md b/docs/user/clusters/config.md index 63d9d8d..df2b236 100644 --- a/docs/user/clusters/config.md +++ b/docs/user/clusters/config.md @@ -51,9 +51,9 @@ The exact contract: implicit current-directory search runs (mode-inference rule 0). Boot from cluster state XOR `omnigraph.yaml`, never a merge. - **The other direction is ergonomics, not coupling**: a per-operator - `omnigraph.yaml` may point `graphs..uri` at a cluster's derived root - (`company-brain/graphs/knowledge.omni`) so data-plane commands can use - `--target ` — an ordinary local path, no special handling. + data-plane commands address a cluster graph by its derived storage root + (`company-brain/graphs/knowledge.omni`) with `--store ` — an ordinary + local path, no special handling. ## Supported `cluster.yaml` diff --git a/docs/user/clusters/index.md b/docs/user/clusters/index.md index 0617753..053d5a1 100644 --- a/docs/user/clusters/index.md +++ b/docs/user/clusters/index.md @@ -246,10 +246,10 @@ with an in-flight apply. human step by design — keep `cluster approve` out of automation. - **`omnigraph.yaml` still has a job**: per-operator settings — your `cli.actor` default for `--as`, CLI defaults, credentials, and data-plane - ergonomics (point `graphs..uri` at a derived root like - `company-brain/graphs/knowledge.omni` to use `--target ` for - loads). It just no longer describes the deployment — a server boots from - one source or the other, never a merge of both. + ergonomics (address a cluster graph by its derived root like + `company-brain/graphs/knowledge.omni` with `--store` for loads). It just no + longer describes the deployment — a server boots from one source or the + other, never a merge of both. ## 7. Maintaining a cluster graph diff --git a/docs/user/operations/maintenance.md b/docs/user/operations/maintenance.md index d9aaa7f..bf7a81c 100644 --- a/docs/user/operations/maintenance.md +++ b/docs/user/operations/maintenance.md @@ -1,6 +1,6 @@ # Maintenance: Optimize, Repair & Cleanup -**Addressing.** `optimize`, `repair`, and `cleanup` are **direct** (storage-native) CLI commands: they run with direct storage access against a positional `URI`, `--target`, or **`--cluster --cluster-graph `** (which resolves the graph's storage URI from the served cluster state, so you needn't know the `/graphs/.omni` layout). They never run through a server, and reject `--server` / `--graph` or a `--target` that resolves to a remote (`http(s)://`) URL with a declared error. There are no server routes for them by design — to maintain a server-backed graph, run them out-of-band against the graph's storage URI. See the *Command capabilities* section of [cli-reference.md](../cli/reference.md). +**Addressing.** `optimize`, `repair`, and `cleanup` are **direct** (storage-native) CLI commands: they run with direct storage access against a positional `file://`/`s3://` URI or **`--cluster --cluster-graph `** (which resolves the graph's storage URI from the served cluster state, so you needn't know the `/graphs/.omni` layout). They never run through a server, and reject `--server` / `--graph` or a remote (`http(s)://`) URI with a declared error. There are no server routes for them by design — to maintain a server-backed graph, run them out-of-band against the graph's storage URI. See the *Command capabilities* section of [cli-reference.md](../cli/reference.md). ## `optimize` — non-destructive diff --git a/docs/user/operations/policy.md b/docs/user/operations/policy.md index 159ed4d..ced1c60 100644 --- a/docs/user/operations/policy.md +++ b/docs/user/operations/policy.md @@ -105,7 +105,7 @@ 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. -- `omnigraph --as ` — set the actor for the duration of one invocation. Effective for `change`, `load` (and its deprecated `ingest` alias), `branch create|delete|merge`, and `schema apply` against local URIs. No-op against remote HTTP URIs (actor is bearer-token-resolved server-side). +- `omnigraph --as ` — set the actor for the duration of one invocation. Effective for `change`, `load` (and its deprecated `ingest` alias), `branch create|delete|merge`, and `schema apply` against a direct (`--store`) graph. **Rejected** on a served write (`--server`): the actor is bearer-token-resolved server-side, so `--as` can't set it there. ## Enforcement diff --git a/docs/user/search/indexes.md b/docs/user/search/indexes.md index ebd69b1..ea65a6f 100644 --- a/docs/user/search/indexes.md +++ b/docs/user/search/indexes.md @@ -23,7 +23,7 @@ list/`Blob` columns → none. > **Coverage and cost.** Each indexed column adds index files and build time, and > an index only covers the fragments it was built over. Rows appended after the > index was built (e.g. by `ingest --mode merge`) are scanned unindexed until a -> reindex extends coverage; see [maintenance](maintenance.md) → `optimize`. +> reindex extends coverage; see [maintenance](../operations/maintenance.md) → `optimize`. ## L2 — OmniGraph orchestration