mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-18 02:24:27 +02:00
feat(cli)!: remove legacy data-plane addressing (--target, positional http→remote, --as-on-served) (#238)
* feat(cli): --server accepts a literal URL (RFC-011 Decision 2) `resolve_server_flag` now treats a `--server` value containing `://` as a literal base URL (trailing slash trimmed; `--graph` appends `/graphs/<id>`), bypassing the operator-config `servers:` registry; a bare name still resolves through the registry. This is the replacement the upcoming `--uri http(s)://` deprecation points at, and a small ergonomic win on its own (`--server https://host` with no config entry). Token resolution for a literal-URL server falls to the legacy OMNIGRAPH_BEARER_TOKEN chain, same as a positional URL today. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(cli): address the parity-matrix arms with global --store/--server flags Prep for removing the positional-http→remote dispatch. The parity harness addressed both arms with a positional graph right after the verb (`omnigraph <verb> <addr> <args…>`), which only parses for top-level verbs — for nested subcommands (`schema show`, `branch list`, …) the address landed in the subcommand slot and BOTH arms failed identically, so the test passed vacuously (matching exit codes, never comparing output). Address both arms with the global flags instead — local `--store <graph>` (embedded), remote `--server <url>` (served) — appended after the verb + args, valid regardless of nesting. The previously-vacuous nested-verb parity checks now actually compare embedded vs remote (and pass — parity holds), and the remote arm no longer relies on the positional-URL dispatch that's about to be removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: --as on a served write is a hard error (was a silent no-op) A served write resolves the actor server-side from the bearer token, so `--as` could never set identity there — it was silently ignored. It now errors (in the remote write factory, before any HTTP call), pointing the user at removing `--as` or writing directly with `--store`. Reads don't carry `--as`, so this is write-path only. BREAKING for any script that passed `--as` to a remote write (it was a no-op, so behavior is unchanged except the now-explicit error). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: a positional/--uri http(s):// URL no longer dispatches to a server Remote graphs must be addressed with `--server <url>` (or a named server / a profile binding one). A positional or `--uri` `http(s)://` URL on a data verb now errors instead of silently routing to the remote HTTP client — the scheme no longer carries transport semantics. The discriminator is `via_server`: a remote URL produced by a server scope is fine; a remote URL from a positional/`--uri` source is rejected (`reject_positional_remote` in both GraphClient factories). Storage verbs are unaffected — they already reject remote URIs through `resolve_local_graph` with the existing "direct (storage-native)" error. Migrated the gh-host keyed-credential system test to `--server <url>` (the literal URL still prefix-matches the operator server for token resolution). BREAKING: scripts addressing a server by a bare URL must switch to `--server <url>`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: remove the --target flag (use --store / --profile / --server) Removes the legacy named-graph flag and threads its parameter out of the whole resolver chain. `--target` resolved a graph name through `omnigraph.yaml`'s `graphs:` map; its replacements (`--store <uri>`, `--profile <name>`, `--server <name>`) all ship. - Drops the 22 `target` clap fields + the `--cluster` exclusion that named it. - Threads `target`/`cli_target` out of `resolve_uri`/`resolve_cli_graph`/ `resolve_local_graph`/`resolve_local_uri`/`resolve_storage_uri`/ `resolve_remote_bearer_token`/`apply_server_flag`/`execute_query_lint`/ `resolve_selected_graph`/`resolve_registry_selection_for_list`/ `execute_queries_{validate,list}`, the two `GraphClient` factories, and `ScopeFlags`/`ResolvedScope`. - Keeps the shared `OmnigraphConfig::resolve_target_uri` 3-arg (server boot uses it); the CLI passes None for the explicit-target arm. The `cli.graph` default (omnigraph.yaml bare-command fallback) is unchanged — its removal belongs to the omnigraph.yaml excision. - Operator/file aliases that bind a `graph` name still work: the name is now resolved to a URI inline (a positional URI wins). - Error messages and `--graph`/`--server`/`--store` help text no longer name `--target`; the queries-list selection hint points at `cli.graph`. BREAKING. Tests updated (named-target resolution rewritten onto `cli.graph`; positional-URI tests unchanged). Full omnigraph-cli suite green (228). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): drop --target and positional-http addressing; --as-on-served is an error Update the user docs for the legacy data-plane addressing removals: - the CLI `--target` flag is gone — address graphs with a positional URI, `--store`, `--profile`, or `--server <name|url>`; - a positional `http(s)://` URI no longer dispatches to a server (use `--server`); - `--as` on a served write is now rejected (was a silent no-op). Touches cli/reference.md (addressing intro, capability table, error examples, scopes), cli/index.md (the remote-read example → --server), operations/maintenance + policy, and the cluster docs' data-plane load guidance. The server's own `--target` boot flag is unchanged (server.md untouched). Also fixes a pre-existing broken maintenance link in search/indexes.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): --store is loudly exclusive with a positional URI / --server; test graphs→Served Address two Greptile findings on the RFC-011 slices: - Slice A (P1): `--store` combined with a positional URI silently dropped the URI (`scope.rs` did `store.or(uri)`); `--store` + `--server` errored with a misleading "positional URI" message. Now both combinations fail loudly with a declared `--store is exclusive with a positional URI and --server` error. - Slice B (P2): the `command_capability` unit test never exercised the one Data→Served refinement (`graphs`); added the assertion so deleting that guard can't pass silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7eeced3e88
commit
bc2a989a7b
20 changed files with 273 additions and 269 deletions
|
|
@ -31,10 +31,10 @@ pub(crate) struct Cli {
|
|||
#[arg(long = "as", global = true, value_name = "ACTOR")]
|
||||
pub(crate) as_actor: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Graph id on a multi-graph `--server` (appends `/graphs/<id>` 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<String>,
|
||||
|
||||
|
|
@ -76,8 +76,6 @@ pub(crate) enum Command {
|
|||
#[arg(hide = true)]
|
||||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
|
|
@ -114,8 +112,6 @@ pub(crate) enum Command {
|
|||
#[arg(hide = true)]
|
||||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
|
|
@ -140,8 +136,6 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
data: PathBuf,
|
||||
|
|
@ -165,8 +159,6 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
data: PathBuf,
|
||||
|
|
@ -189,8 +181,6 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
branch: Option<String>,
|
||||
|
|
@ -202,8 +192,6 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
branch: Option<String>,
|
||||
|
|
@ -250,12 +238,10 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
/// 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<String>,
|
||||
/// Graph id within --cluster.
|
||||
#[arg(long, requires = "cluster")]
|
||||
|
|
@ -268,12 +254,10 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
/// 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<String>,
|
||||
/// Graph id within --cluster.
|
||||
#[arg(long, requires = "cluster")]
|
||||
|
|
@ -294,12 +278,10 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
/// 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<String>,
|
||||
/// Graph id within --cluster.
|
||||
#[arg(long, requires = "cluster")]
|
||||
|
|
@ -333,8 +315,6 @@ pub(crate) enum Command {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
query: PathBuf,
|
||||
|
|
@ -489,8 +469,6 @@ pub(crate) enum GraphsCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
|
@ -505,8 +483,6 @@ pub(crate) enum BranchCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
from: Option<String>,
|
||||
|
|
@ -520,8 +496,6 @@ pub(crate) enum BranchCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
|
@ -532,8 +506,6 @@ pub(crate) enum BranchCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
name: String,
|
||||
#[arg(long)]
|
||||
|
|
@ -545,8 +517,6 @@ pub(crate) enum BranchCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
source: String,
|
||||
#[arg(long)]
|
||||
|
|
@ -563,8 +533,6 @@ pub(crate) enum SchemaCommand {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
schema: PathBuf,
|
||||
|
|
@ -581,8 +549,6 @@ pub(crate) enum SchemaCommand {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
schema: PathBuf,
|
||||
|
|
@ -606,8 +572,6 @@ pub(crate) enum SchemaCommand {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
|
|
@ -622,8 +586,6 @@ pub(crate) enum CommitCommand {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
branch: Option<String>,
|
||||
|
|
@ -636,8 +598,6 @@ pub(crate) enum CommitCommand {
|
|||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
commit_id: String,
|
||||
#[arg(long)]
|
||||
|
|
@ -684,16 +644,12 @@ pub(crate) enum QueriesCommand {
|
|||
/// Graph URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// List the registered stored queries (name, MCP exposure, params).
|
||||
List {
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
|
|
|
|||
|
|
@ -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 <url>` — 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<String>,
|
||||
target: Option<&str>,
|
||||
profile: Option<&str>,
|
||||
store: Option<&str>,
|
||||
) -> Result<Self> {
|
||||
// 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<String>,
|
||||
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 <uri>`."
|
||||
);
|
||||
}
|
||||
Ok(GraphClient::Remote {
|
||||
http: build_http_client()?,
|
||||
base_url: resolved.uri,
|
||||
|
|
|
|||
|
|
@ -245,8 +245,9 @@ pub(crate) fn normalize_policy_graph_uri(uri: &str) -> Result<String> {
|
|||
pub(crate) fn resolve_remote_bearer_token(
|
||||
config: &OmnigraphConfig,
|
||||
explicit_uri: Option<&str>,
|
||||
explicit_target: Option<&str>,
|
||||
) -> Result<Option<String>> {
|
||||
// `--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_<NAME> 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::<Vec<_>>()
|
||||
.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::<Vec<_>>()
|
||||
.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<ReadOutput> {
|
||||
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<String>,
|
||||
target: Option<&str>,
|
||||
) -> Result<Option<String>> {
|
||||
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<T: DeserializeOwned>(
|
|||
Ok(serde_json::from_str(&text)?)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_uri(
|
||||
config: &OmnigraphConfig,
|
||||
cli_uri: Option<String>,
|
||||
cli_target: Option<&str>,
|
||||
) -> Result<String> {
|
||||
config.resolve_target_uri(cli_uri, cli_target, config.cli_graph_name())
|
||||
pub(crate) fn resolve_uri(config: &OmnigraphConfig, cli_uri: Option<String>) -> Result<String> {
|
||||
// `--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<String>,
|
||||
cli_target: Option<&str>,
|
||||
) -> Result<ResolvedCliGraph> {
|
||||
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<String>,
|
||||
cli_target: Option<&str>,
|
||||
operation: &str,
|
||||
) -> Result<ResolvedCliGraph> {
|
||||
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<std::time::Duration> {
|
|||
pub(crate) fn resolve_local_uri(
|
||||
config: &OmnigraphConfig,
|
||||
cli_uri: Option<String>,
|
||||
cli_target: Option<&str>,
|
||||
operation: &str,
|
||||
) -> Result<String> {
|
||||
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 <dir|uri> --cluster-graph <id>` 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<String>,
|
||||
cli_target: Option<&str>,
|
||||
cluster: Option<&str>,
|
||||
cluster_graph: Option<&str>,
|
||||
operation: &str,
|
||||
) -> Result<String> {
|
||||
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<String>,
|
||||
cli_target: Option<&str>,
|
||||
schema_path: Option<&PathBuf>,
|
||||
query_path: &PathBuf,
|
||||
) -> Result<QueryLintOutput> {
|
||||
|
|
@ -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 <schema.pg> 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<String>,
|
||||
cli_target: Option<&str>,
|
||||
operation: &str,
|
||||
) -> Result<(String, Option<String>)> {
|
||||
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<Option<String>> {
|
||||
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<String>,
|
||||
target: Option<String>,
|
||||
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<String>,
|
||||
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<OsString>) -> Vec<OsString> {
|
|||
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 `<base-path>/branches/<name>` with no empty `//`
|
||||
// segment — an empty segment misses the
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 <name>, a storage URI, or --cluster <dir> --cluster-graph <id>.",
|
||||
_ => "Pass a storage URI, or --cluster <dir> --cluster-graph <id>.",
|
||||
},
|
||||
Capability::Control => "It operates on a cluster (pass --config <dir>).",
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -32,24 +32,22 @@ pub(crate) struct ResolvedScope {
|
|||
pub(crate) server: Option<String>,
|
||||
pub(crate) graph: Option<String>,
|
||||
pub(crate) uri: Option<String>,
|
||||
pub(crate) target: Option<String>,
|
||||
pub(crate) cluster: Option<String>,
|
||||
pub(crate) cluster_graph: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
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<ResolvedScope> {
|
||||
// `--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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue