feat(cli)!: schema apply refuses a cluster-managed graph (RFC-011 D10) (#253)

`omnigraph schema apply` against a cluster-managed graph's storage root bypassed
the cluster ledger/recovery/approvals. Mirror `init`'s refusal: on the embedded
(direct-store) path, if the resolved URI is inside a cluster
(`cluster_root_for_graph_uri`), bail and point at `cluster apply`. The served
(`--server`) path is unaffected — it addresses a server, not a storage root.
`schema plan`/`show` (read-only) are untouched.

Two e2e tests injected "out-of-band drift" via this exact CLI path; since the
CLI now refuses it, they inject drift via a direct engine `apply_schema` against
the storage root instead — a faithful control-plane bypass, which is what
out-of-band drift is. New regression:
`schema_apply_refuses_a_cluster_managed_graph_and_signposts_cluster_apply`.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andrew Altshuler 2026-06-15 23:11:42 +03:00 committed by GitHub
parent 4601e5f4bf
commit d2340f19e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89 additions and 33 deletions

View file

@ -3,6 +3,7 @@
use std::fs;
use omnigraph::db::Omnigraph;
use tempfile::tempdir;
mod support;
@ -236,27 +237,28 @@ fn cluster_e2e_out_of_band_schema_drift_then_apply_converges_it() {
let apply = cluster_json(temp.path(), "apply");
assert_eq!(apply["converged"], true, "{apply}");
// Out-of-band: the live graph evolves, cluster.yaml stays put.
fs::write(
temp.path().join("people_v2.pg"),
r#"
// Out-of-band: the live graph evolves while cluster.yaml stays put. RFC-011
// D10 makes the CLI `schema apply` refuse a cluster-managed graph, so this
// simulates a true bypass — a direct engine apply against the storage root,
// exactly the drift the control plane must still detect and converge.
let people_v2 = r#"
node Person {
name: String @key
age: I32?
bio: String?
}
"#,
)
.unwrap();
output_success(
cli()
.arg("schema")
.arg("apply")
.arg(temp.path().join("graphs/knowledge.omni"))
.arg("--schema")
.arg(temp.path().join("people_v2.pg"))
.arg("--json"),
);
"#;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let db = Omnigraph::open(
temp.path()
.join("graphs/knowledge.omni")
.to_string_lossy()
.as_ref(),
)
.await
.unwrap();
db.apply_schema(people_v2).await.unwrap();
});
// Drift is visible...
let refresh = cluster_json(temp.path(), "refresh");