mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-07-24 03:51:02 +02:00
Merge origin/main into MR-656; retrofit + fold-in run_query
Resolves the 4 hard conflicts from PR #119 (multi-graph server mode, MR-668) landing on main: * `crates/omnigraph-cli/src/main.rs` imports: drop unused `ChangeRequest`, take main's `GraphListResponse`. * `crates/omnigraph-server/src/api.rs`: keep branch's `ChangeRequest` field rename (`query_source` -> `query` with serde alias, `query_name` -> `name`); accept main's rustfmt. * `crates/omnigraph-server/src/lib.rs`: take both import lists (branch's `QueryRequest` + main's `GraphInfo`/`GraphListResponse`); rewrite the `server_change` signature to combine the branch's `run_mutate` extraction with main's `Extension<Arc<GraphHandle>>` + `ResolvedActor` parameter shape. * `docs/user/server.md`: re-apply the branch's new `/query` and `/mutate` rows plus deprecation notes for `/read` and `/change` on top of main's two-column (single-mode | multi-mode) table layout. Auto-merged but stale callsites repaired alongside the conflict resolutions so the merge commit compiles: * `server_query` handler now takes `Extension(handle): Extension<Arc<GraphHandle>>` and `Option<Extension<ResolvedActor>>`, with policy read from `handle.policy.as_deref()` instead of the removed `state.policy_engine()`. Fold-in for MR-969 (next-step seam): * Extract `run_query` mirroring `run_mutate`: both helpers now take `(state, handle, actor, query: &str, name: Option<&str>, params_json: Option<&Value>, branch, ...)` instead of the `QueryRequest` / `ChangeRequest` body type. The future `/queries/{name}` handler can call these with registry-supplied fields without rebuilding the request shape. * `server_query` / `server_read` now route through `run_query`; `server_mutate` / `server_change` route through `run_mutate`. * D2 mutation rejection on `/query` is preserved via the `reject_mutations` flag; `/read` keeps the legacy permissive behavior for byte-stable back-compat. `cargo test -p omnigraph-server --test server`: 89 passed, 0 failed. `cargo build --workspace --tests --locked`: clean. Refs: MR-656, MR-668, MR-969.
This commit is contained in:
commit
221f427a73
58 changed files with 5898 additions and 887 deletions
|
|
@ -18,10 +18,11 @@ use omnigraph_compiler::{
|
|||
};
|
||||
use omnigraph_server::api::{
|
||||
BranchCreateOutput, BranchCreateRequest, BranchDeleteOutput, BranchListOutput,
|
||||
BranchMergeOutput, BranchMergeRequest, ChangeOutput, CommitListOutput,
|
||||
CommitOutput, ErrorOutput, ExportRequest, IngestOutput, IngestRequest, ReadOutput, ReadRequest,
|
||||
SchemaApplyOutput, SchemaApplyRequest, SchemaOutput, SnapshotOutput, SnapshotTableOutput,
|
||||
commit_output, ingest_output, read_output, schema_apply_output, snapshot_payload,
|
||||
BranchMergeOutput, BranchMergeRequest, ChangeOutput, CommitListOutput, CommitOutput,
|
||||
ErrorOutput, ExportRequest, GraphListResponse, IngestOutput, IngestRequest, ReadOutput,
|
||||
ReadRequest, SchemaApplyOutput, SchemaApplyRequest, SchemaOutput, SnapshotOutput,
|
||||
SnapshotTableOutput, commit_output, ingest_output, read_output, schema_apply_output,
|
||||
snapshot_payload,
|
||||
};
|
||||
use omnigraph_server::{
|
||||
AliasCommand, OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest,
|
||||
|
|
@ -73,6 +74,13 @@ enum Command {
|
|||
schema: PathBuf,
|
||||
/// Graph URI (local path or s3://)
|
||||
uri: String,
|
||||
/// Overwrite existing schema artifacts at the URI. Without
|
||||
/// this flag, init refuses to touch a URI that already holds
|
||||
/// `_schema.pg`, `_schema.ir.json`, or `__schema_state.json`
|
||||
/// — closes the re-init footgun (MR-668 follow-up). With the
|
||||
/// flag, the operator opts in to destructive semantics.
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
},
|
||||
/// Load data into a graph
|
||||
Load {
|
||||
|
|
@ -285,6 +293,33 @@ enum Command {
|
|||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Manage graphs on a multi-graph server (MR-668)
|
||||
Graphs {
|
||||
#[command(subcommand)]
|
||||
command: GraphsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
/// Operations on the graph registry of a multi-graph server (MR-668).
|
||||
///
|
||||
/// All operations target a remote multi-graph server URL (http:// or
|
||||
/// https://). Local-URI invocations return a clear error. To add or
|
||||
/// remove graphs, operators edit `omnigraph.yaml` directly and restart
|
||||
/// the server — runtime mutation is not exposed in v0.6.0.
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum GraphsCommand {
|
||||
/// List every graph registered with the multi-graph server.
|
||||
List {
|
||||
/// Remote server URL (e.g. `https://server.example.com`).
|
||||
#[arg(long)]
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
|
|
@ -707,7 +742,7 @@ fn resolve_policy_engine(config: &OmnigraphConfig) -> Result<PolicyEngine> {
|
|||
let policy_file = config
|
||||
.resolve_policy_file()
|
||||
.ok_or_else(|| color_eyre::eyre::eyre!("policy.file must be set in omnigraph.yaml"))?;
|
||||
PolicyEngine::load(&policy_file, &policy_graph_id(config))
|
||||
PolicyEngine::load_graph(&policy_file, &policy_graph_id(config))
|
||||
}
|
||||
|
||||
/// Open a local-URI graph and, when `policy.file` is configured in
|
||||
|
|
@ -1327,12 +1362,12 @@ fn print_commit_human(commit: &CommitOutput) {
|
|||
println!("created_at: {}", commit.created_at);
|
||||
}
|
||||
|
||||
fn print_policy_explain(decision: &PolicyDecision, request: &PolicyRequest) {
|
||||
fn print_policy_explain(decision: &PolicyDecision, actor_id: &str, request: &PolicyRequest) {
|
||||
println!(
|
||||
"decision: {}",
|
||||
if decision.allowed { "allow" } else { "deny" }
|
||||
);
|
||||
println!("actor: {}", request.actor_id);
|
||||
println!("actor: {}", actor_id);
|
||||
println!("action: {}", request.action);
|
||||
if let Some(branch) = &request.branch {
|
||||
println!("branch: {}", branch);
|
||||
|
|
@ -1807,10 +1842,15 @@ async fn main() -> Result<()> {
|
|||
print_embed_human(&output);
|
||||
}
|
||||
}
|
||||
Command::Init { schema, uri } => {
|
||||
Command::Init { schema, uri, force } => {
|
||||
let schema_source = fs::read_to_string(&schema)?;
|
||||
ensure_local_graph_parent(&uri)?;
|
||||
Omnigraph::init(&uri, &schema_source).await?;
|
||||
Omnigraph::init_with_options(
|
||||
&uri,
|
||||
&schema_source,
|
||||
omnigraph::db::InitOptions { force },
|
||||
)
|
||||
.await?;
|
||||
scaffold_config_if_missing(&uri)?;
|
||||
println!("initialized {}", uri);
|
||||
}
|
||||
|
|
@ -2534,13 +2574,12 @@ async fn main() -> Result<()> {
|
|||
let config = load_cli_config(config.as_ref())?;
|
||||
let engine = resolve_policy_engine(&config)?;
|
||||
let request = PolicyRequest {
|
||||
actor_id: actor,
|
||||
action,
|
||||
branch,
|
||||
target_branch,
|
||||
};
|
||||
let decision = engine.authorize(&request)?;
|
||||
print_policy_explain(&decision, &request);
|
||||
let decision = engine.authorize(&actor, &request)?;
|
||||
print_policy_explain(&decision, &actor, &request);
|
||||
}
|
||||
},
|
||||
Command::Optimize {
|
||||
|
|
@ -2647,6 +2686,41 @@ async fn main() -> Result<()> {
|
|||
);
|
||||
}
|
||||
}
|
||||
Command::Graphs { command } => match command {
|
||||
GraphsCommand::List {
|
||||
uri,
|
||||
target,
|
||||
config,
|
||||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let bearer_token =
|
||||
resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?;
|
||||
let uri = resolve_uri(&config, uri, target.as_deref())?;
|
||||
if !is_remote_uri(&uri) {
|
||||
bail!(
|
||||
"`omnigraph graphs list` requires a remote multi-graph server URL \
|
||||
(http:// or https://). To enumerate local graphs, read `omnigraph.yaml` \
|
||||
directly."
|
||||
);
|
||||
}
|
||||
let payload = remote_json::<GraphListResponse>(
|
||||
&http_client,
|
||||
Method::GET,
|
||||
remote_url(&uri, "/graphs"),
|
||||
None,
|
||||
bearer_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
if json {
|
||||
print_json(&payload)?;
|
||||
} else {
|
||||
for entry in payload.graphs {
|
||||
println!("{}\t{}", entry.graph_id, entry.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2261,3 +2261,47 @@ fn schema_plan_parity_cli_and_sdk() {
|
|||
);
|
||||
assert_eq!(cli_payload["supported"], plan.supported);
|
||||
}
|
||||
|
||||
// ─── MR-668 PR 8 — omnigraph graphs subcommand ─────────────────────────────
|
||||
|
||||
/// `omnigraph graphs --help` lists only the read-only `list`
|
||||
/// subcommand. Runtime add (`create`) and remove (`delete`) are
|
||||
/// deferred — operators add/remove graphs by editing `omnigraph.yaml`
|
||||
/// and restarting. This test pins the deferral against accidental
|
||||
/// re-introduction.
|
||||
#[test]
|
||||
fn graphs_subcommand_help_lists_list_only() {
|
||||
let output = output_success(cli().arg("graphs").arg("--help"));
|
||||
let stdout = stdout_string(&output);
|
||||
assert!(
|
||||
stdout.contains("list"),
|
||||
"expected `list` subcommand in help output:\n{stdout}"
|
||||
);
|
||||
let lowered = stdout.to_lowercase();
|
||||
assert!(
|
||||
!lowered.contains("create a new graph"),
|
||||
"graph create should not be in v0.6.0 help; got:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
!lowered.contains("delete a graph"),
|
||||
"graph delete should not be in v0.6.0 help; got:\n{stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
/// `omnigraph graphs list` against a local URI errors with a clear
|
||||
/// message — the CLI only operates against remote multi-graph servers.
|
||||
#[test]
|
||||
fn graphs_list_against_local_uri_errors_with_remote_only_message() {
|
||||
let output = output_failure(
|
||||
cli()
|
||||
.arg("graphs")
|
||||
.arg("list")
|
||||
.arg("--uri")
|
||||
.arg("/tmp/local"),
|
||||
);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||
assert!(
|
||||
stderr.contains("remote multi-graph server URL"),
|
||||
"expected 'remote multi-graph server URL' rejection in stderr; got:\n{stderr}"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,17 @@ rules:
|
|||
target_branch_scope: protected
|
||||
"#;
|
||||
|
||||
const GRAPH_LIST_SERVER_POLICY_YAML: &str = r#"
|
||||
version: 1
|
||||
groups:
|
||||
admins: [act-admin]
|
||||
rules:
|
||||
- id: admins-can-list-graphs
|
||||
allow:
|
||||
actors: { group: admins }
|
||||
actions: [graph_list]
|
||||
"#;
|
||||
|
||||
fn yaml_string(value: &str) -> String {
|
||||
format!("'{}'", value.replace('\'', "''"))
|
||||
}
|
||||
|
|
@ -949,3 +960,112 @@ query insert_person($name: String, $age: I32) {
|
|||
assert_eq!(verify["row_count"], 1);
|
||||
assert_eq!(verify["rows"][0]["p.name"], "PolicyRemote");
|
||||
}
|
||||
|
||||
// ─── MR-668 PR 8 — omnigraph graphs list end-to-end ────────────────────────
|
||||
|
||||
/// Multi-graph server + CLI `omnigraph graphs list` end-to-end.
|
||||
///
|
||||
/// Steps:
|
||||
/// 1. Init a graph `alpha` on disk and write an `omnigraph.yaml`
|
||||
/// whose `graphs:` map references it.
|
||||
/// 2. Spawn the server with `--config <yaml>`.
|
||||
/// 3. `omnigraph graphs list` — expect to see `alpha`.
|
||||
///
|
||||
/// Ignored by default — spawning servers needs loopback socket
|
||||
/// permissions some sandboxes lack.
|
||||
#[test]
|
||||
#[ignore = "requires loopback socket permissions in sandboxed runners"]
|
||||
fn graphs_list_against_multi_graph_server() {
|
||||
let cfg_dir = tempfile::tempdir().unwrap();
|
||||
let schema_path = fixture("test.pg");
|
||||
|
||||
// Init `alpha` on disk.
|
||||
let alpha_uri = cfg_dir.path().join("alpha.omni");
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
Omnigraph::init(
|
||||
alpha_uri.to_str().unwrap(),
|
||||
&fs::read_to_string(&schema_path).unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
fs::write(
|
||||
cfg_dir.path().join("server-policy.yaml"),
|
||||
GRAPH_LIST_SERVER_POLICY_YAML,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Server config with `graphs:` map and no `server.graph` selector
|
||||
// — multi mode (rule 4 of the inference matrix). `GET /graphs` is a
|
||||
// server-scoped action, so the success path needs an explicit server
|
||||
// policy and bearer token.
|
||||
let server_config_path = cfg_dir.path().join("omnigraph.yaml");
|
||||
fs::write(
|
||||
&server_config_path,
|
||||
format!(
|
||||
"\
|
||||
server:
|
||||
policy:
|
||||
file: ./server-policy.yaml
|
||||
graphs:
|
||||
alpha:
|
||||
uri: {}
|
||||
",
|
||||
yaml_string(&alpha_uri.to_string_lossy())
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let server = spawn_server_with_config_env(
|
||||
&server_config_path,
|
||||
&[(
|
||||
"OMNIGRAPH_SERVER_BEARER_TOKENS_JSON",
|
||||
r#"{"act-admin":"admin-token"}"#,
|
||||
)],
|
||||
);
|
||||
|
||||
// Client config — the CLI's `--target dev` resolves to `server.base_url`.
|
||||
let client_config_path = cfg_dir.path().join("client.yaml");
|
||||
fs::write(
|
||||
&client_config_path,
|
||||
format!(
|
||||
"\
|
||||
graphs:
|
||||
dev:
|
||||
uri: {}
|
||||
bearer_token_env: GRAPH_LIST_TOKEN
|
||||
cli:
|
||||
graph: dev
|
||||
auth:
|
||||
env_file: ./.env.omni
|
||||
",
|
||||
yaml_string(&server.base_url)
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
cfg_dir.path().join(".env.omni"),
|
||||
"GRAPH_LIST_TOKEN=admin-token\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// `graphs list` lists `alpha`.
|
||||
let payload = parse_stdout_json(&output_success(
|
||||
cli()
|
||||
.arg("graphs")
|
||||
.arg("list")
|
||||
.arg("--config")
|
||||
.arg(&client_config_path)
|
||||
.arg("--json"),
|
||||
));
|
||||
let ids: Vec<&str> = payload["graphs"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|g| g["graph_id"].as_str().unwrap())
|
||||
.collect();
|
||||
assert_eq!(ids, vec!["alpha"]);
|
||||
|
||||
drop(server);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue