mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-24 02:38:06 +02:00
feat(cli): RFC-011 Slice A — additive scope/profile addressing (#235)
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
CI / Container Entrypoint (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-windows-x86_64 (push) Has been cancelled
Release Edge / Smoke Windows installer (push) Has been cancelled
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
CI / Container Entrypoint (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-windows-x86_64 (push) Has been cancelled
Release Edge / Smoke Windows installer (push) Has been cancelled
* feat(cli): RFC-011 Slice A — operator-config scope structs (profiles/clusters/defaults)
Additive operator-config surface for the RFC-011 scope model. No behavior
change yet — these structs are parsed but not consumed until the scope
resolver lands.
- OperatorConfig gains `profiles:` (name → OperatorProfile) and `clusters:`
(name → OperatorCluster { root }) — the latter the only place a storage
root appears in operator config (RFC-011 storage-root rule).
- OperatorDefaults gains `server` and `default_graph` (the flat-default scope).
- OperatorProfile binds one of {server, cluster, store} + default_graph;
`binding()` validates exactly-one on use and returns a ScopeBinding.
- Accessors profile()/cluster_root()/default_server()/default_graph();
unknown-key warnings extended to the new blocks (forward-compat preserved —
old configs still load, new keys are no longer "unknown").
Tests: parse profiles/clusters/scope-defaults, binding rejects zero/multiple
entities, unknown keys in a profile warn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): RFC-011 Slice A — scope resolver + --profile/--store, wired (additive)
Translate the new scope inputs into the existing addressing tuple, in front of
the unchanged resolvers. Purely additive: an explicit address
(--uri/--target/--server/--store) passes straight through, so every existing
invocation is byte-for-byte unchanged.
- scope.rs: resolve_scope() with the RFC-011 precedence (explicit > --profile /
OMNIGRAPH_PROFILE > flat defaults.server), producing the effective
(server, graph, uri, target) for data verbs and (cluster, cluster_graph) for
maintenance. Plane×scope capability check (server scope rejected on a
maintenance verb; cluster scope rejected on a data verb; store rejects --graph)
fires only on the new paths. 9 unit tests.
- cli.rs: global --profile <NAME> and --store <URI>. (--graph keeps
requires=server for now; profile/default graph comes from default_graph —
profile+--graph override is deferred to the --cluster-graph rework.)
- client.rs: the two GraphClient factories call resolve_scope (Plane::Data) up
front; the explicit branch reproduces today's behavior exactly.
- main.rs: the 15 data call sites forward --profile/--store; the 3 maintenance
verbs consult the scope (Plane::Storage) only when no explicit per-command
address is given, so cluster-binding profiles and --store reach
optimize/repair/cleanup.
Verified: the full omnigraph-cli suite (221 tests) stays green untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test+docs(cli): RFC-011 Slice A — end-to-end scope test + reference docs
- cli_data.rs: prove --store and a --profile store binding drive a read
identically to the legacy positional URI (the additive-coexistence contract),
end to end against a local graph (no server needed).
- cli/reference.md: document profiles/clusters/defaults.server/default_graph,
the --profile/--store flags, and a "Scopes & profiles" section; note the model
coexists with legacy addressing (nothing removed yet).
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
ceb37dd4cb
commit
a4d08a4184
7 changed files with 777 additions and 28 deletions
|
|
@ -49,6 +49,7 @@ mod cli;
|
|||
mod client;
|
||||
mod helpers;
|
||||
mod output;
|
||||
mod scope;
|
||||
mod planes;
|
||||
use cli::*;
|
||||
use helpers::*;
|
||||
|
|
@ -185,6 +186,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
let payload = client
|
||||
|
|
@ -219,6 +222,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
let from = resolve_branch(&config, from, None, "main");
|
||||
|
|
@ -248,6 +253,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let from = resolve_branch(&config, from, None, "main");
|
||||
let payload = client.branch_create_from(&from, &name).await?;
|
||||
|
|
@ -270,6 +277,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let payload = client.branch_list().await?;
|
||||
if json {
|
||||
|
|
@ -295,6 +304,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let payload = client.branch_delete(&name).await?;
|
||||
if json {
|
||||
|
|
@ -319,6 +330,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let into = resolve_branch(&config, into, None, "main");
|
||||
let payload = client.branch_merge(&source, &into).await?;
|
||||
|
|
@ -349,6 +362,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let payload = client.list_commits(branch.as_deref()).await?;
|
||||
if json {
|
||||
|
|
@ -371,6 +386,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let commit = client.get_commit(&commit_id).await?;
|
||||
if json {
|
||||
|
|
@ -427,6 +444,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target.as_deref(),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let schema_source = fs::read_to_string(&schema)?;
|
||||
// The stored-query registry check is an embedded-only concern
|
||||
|
|
@ -467,6 +486,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let output = client.schema_source().await?;
|
||||
if json {
|
||||
|
|
@ -521,6 +542,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
let payload = client.snapshot(&branch).await?;
|
||||
|
|
@ -546,6 +569,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
if jsonl {
|
||||
|
|
@ -636,6 +661,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target_name,
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
|
|
@ -714,6 +741,8 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
target_name,
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
|
|
@ -798,15 +827,41 @@ async fn main() -> Result<()> {
|
|||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let uri = resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"optimize",
|
||||
)
|
||||
.await?;
|
||||
let uri = if uri.is_some() || target.is_some() || cluster.is_some() {
|
||||
resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"optimize",
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// RFC-011: no explicit per-command address — consult the scope
|
||||
// (a --profile cluster binding, --store, or operator defaults).
|
||||
let scope = scope::resolve_scope(
|
||||
&operator::load_operator_config()?,
|
||||
planes::Plane::Storage,
|
||||
scope::ScopeFlags {
|
||||
profile: cli.profile.as_deref(),
|
||||
store: cli.store.as_deref(),
|
||||
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",
|
||||
)
|
||||
.await?
|
||||
};
|
||||
let db = Omnigraph::open(&uri).await?;
|
||||
let stats = db.optimize().await?;
|
||||
if json {
|
||||
|
|
@ -850,15 +905,40 @@ async fn main() -> Result<()> {
|
|||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let uri = resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"repair",
|
||||
)
|
||||
.await?;
|
||||
let uri = if uri.is_some() || target.is_some() || cluster.is_some() {
|
||||
resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"repair",
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// RFC-011: no explicit per-command address — consult the scope.
|
||||
let scope = scope::resolve_scope(
|
||||
&operator::load_operator_config()?,
|
||||
planes::Plane::Storage,
|
||||
scope::ScopeFlags {
|
||||
profile: cli.profile.as_deref(),
|
||||
store: cli.store.as_deref(),
|
||||
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",
|
||||
)
|
||||
.await?
|
||||
};
|
||||
let db = Omnigraph::open(&uri).await?;
|
||||
let stats = db
|
||||
.repair(omnigraph::db::RepairOptions { confirm, force })
|
||||
|
|
@ -944,15 +1024,40 @@ async fn main() -> Result<()> {
|
|||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let uri = resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"cleanup",
|
||||
)
|
||||
.await?;
|
||||
let uri = if uri.is_some() || target.is_some() || cluster.is_some() {
|
||||
resolve_storage_uri(
|
||||
&config,
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cluster.as_deref(),
|
||||
cluster_graph.as_deref(),
|
||||
"cleanup",
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// RFC-011: no explicit per-command address — consult the scope.
|
||||
let scope = scope::resolve_scope(
|
||||
&operator::load_operator_config()?,
|
||||
planes::Plane::Storage,
|
||||
scope::ScopeFlags {
|
||||
profile: cli.profile.as_deref(),
|
||||
store: cli.store.as_deref(),
|
||||
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",
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let older_than_dur = older_than.as_deref().map(parse_duration_arg).transpose()?;
|
||||
|
||||
|
|
@ -1088,6 +1193,8 @@ async fn main() -> Result<()> {
|
|||
cli.graph.as_deref(),
|
||||
uri,
|
||||
target.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let payload = client.list_graphs().await?;
|
||||
if json {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue