Skip to content

Commit 46b6d90

Browse files
authored
Implement RFC-028 stable schema identity (#357)
* feat(schema): add stable identity IR v2 * feat(schema): activate stable identity contract * feat(schema): validate manifest identity contract * feat(schema): preserve stable identities through apply * feat(manifest): key tables and recovery by stable identity * docs: align agent guide with stable identities * test: cover stable table identity behavior * test: resolve identity-derived maintenance paths * test: bind recovery fixtures to table identity * fix(recovery): fence stable table ownership * fix(schema): close stable identity compiler gaps * feat(schema): complete stable identity integration * docs: finalize stable identity contract
1 parent f8c7f7b commit 46b6d90

71 files changed

Lines changed: 9152 additions & 2656 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 11 additions & 10 deletions
Large diffs are not rendered by default.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/omnigraph-cli/tests/crossversion_upgrade.rs

Lines changed: 138 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
//! Cross-version upgrade: prove the CURRENT binary handles a GENUINE old-format
2-
//! (internal schema v3) graph minted by omnigraph 0.7.2 — not a v4-shaped graph
3-
//! with a rewound stamp. Two things the stamp-rewind stand-in
1+
//! Cross-version upgrade: prove the CURRENT binary handles GENUINE old-format
2+
//! graphs minted by released binaries — not a current-shaped graph with a
3+
//! rewound stamp. Two things the stamp-rewind stand-in
44
//! (`sub_current_graph_is_refused_then_rebuilt_via_export_import`) cannot prove:
55
//!
66
//! 1. the open-refusal fires on the REAL on-disk v3 shape (lineage in
@@ -9,11 +9,10 @@
99
//! 2. the documented `export → init → load` rebuild round-trips the data,
1010
//! including a `Vector` column, off a genuine v3 export.
1111
//!
12-
//! Gated: requires `OMNIGRAPH_OLD_BIN` (an absolute path to a 0.7.2 `omnigraph`
13-
//! binary). Skips gracefully when unset — the same convention as the S3 and
14-
//! system-e2e gates — so the default `cargo test --workspace` stays green
15-
//! without it. CI sets it in the `crossversion_upgrade` job (see
16-
//! `docs/dev/testing.md`).
12+
//! The v3 case uses `OMNIGRAPH_OLD_BIN` (0.7.2). The immediate-predecessor v4
13+
//! case uses `OMNIGRAPH_PREVIOUS_BIN` (0.8.1) and also proves that binary
14+
//! refuses a v5 graph. Each case skips only when its variable is unset; a set
15+
//! but invalid path fails loudly.
1716
1817
mod support;
1918

@@ -39,6 +38,17 @@ fn old_bin() -> Option<PathBuf> {
3938
Some(path)
4039
}
4140

41+
fn previous_bin() -> Option<PathBuf> {
42+
let path = PathBuf::from(std::env::var_os("OMNIGRAPH_PREVIOUS_BIN")?);
43+
assert!(
44+
path.exists(),
45+
"OMNIGRAPH_PREVIOUS_BIN is set but does not exist: {} \
46+
(unset it to skip, or point it at a real 0.8.1 omnigraph binary)",
47+
path.display(),
48+
);
49+
Some(path)
50+
}
51+
4252
/// Run the OLD (0.7.2) binary hermetically (no developer `~/.omnigraph`).
4353
fn run_old(bin: &Path, args: &[&str]) -> std::process::Output {
4454
Command::new(bin)
@@ -65,6 +75,15 @@ fn nonblank_lines(bytes: &[u8]) -> usize {
6575
.count()
6676
}
6777

78+
fn exported_row_with_slug(bytes: &[u8], slug: &str) -> serde_json::Value {
79+
String::from_utf8_lossy(bytes)
80+
.lines()
81+
.filter(|line| !line.trim().is_empty())
82+
.map(|line| serde_json::from_str::<serde_json::Value>(line).expect("valid export JSONL"))
83+
.find(|row| row["data"]["slug"].as_str() == Some(slug))
84+
.unwrap_or_else(|| panic!("export must contain slug '{slug}'"))
85+
}
86+
6887
#[test]
6988
fn current_binary_refuses_and_rebuilds_a_genuine_v3_graph() {
7089
let Some(old) = old_bin() else {
@@ -91,7 +110,14 @@ fn current_binary_refuses_and_rebuilds_a_genuine_v3_graph() {
91110
"load",
92111
&run_old(
93112
&old,
94-
&["load", "--mode", "overwrite", "--data", data.to_str().unwrap(), og],
113+
&[
114+
"load",
115+
"--mode",
116+
"overwrite",
117+
"--data",
118+
data.to_str().unwrap(),
119+
og,
120+
],
95121
),
96122
);
97123

@@ -124,8 +150,14 @@ fn current_binary_refuses_and_rebuilds_a_genuine_v3_graph() {
124150
);
125151

126152
// 4. The CURRENT binary rebuilds: fresh init + load the v3 export.
127-
let new_graph = temp.path().join("new-v4.omni");
128-
output_success(cli().arg("init").arg("--schema").arg(&schema).arg(&new_graph));
153+
let new_graph = temp.path().join("new-current.omni");
154+
output_success(
155+
cli()
156+
.arg("init")
157+
.arg("--schema")
158+
.arg(&schema)
159+
.arg(&new_graph),
160+
);
129161
output_success(
130162
cli()
131163
.arg("load")
@@ -141,7 +173,7 @@ fn current_binary_refuses_and_rebuilds_a_genuine_v3_graph() {
141173
assert_eq!(
142174
nonblank_lines(&export.stdout),
143175
nonblank_lines(&reexport.stdout),
144-
"row count must round-trip v3 → v4",
176+
"row count must round-trip v3 → current",
145177
);
146178
let rebuilt = String::from_utf8_lossy(&reexport.stdout);
147179
assert!(
@@ -153,3 +185,97 @@ fn current_binary_refuses_and_rebuilds_a_genuine_v3_graph() {
153185
"the rebuilt graph must preserve node data, got: {rebuilt}",
154186
);
155187
}
188+
189+
#[test]
190+
fn v5_refuses_and_rebuilds_genuine_v4_and_v4_refuses_v5() {
191+
let Some(previous) = previous_bin() else {
192+
eprintln!(
193+
"skipping immediate-predecessor upgrade test: OMNIGRAPH_PREVIOUS_BIN is not set to a 0.8.1 binary"
194+
);
195+
return;
196+
};
197+
198+
let temp = tempdir().unwrap();
199+
let old_graph = temp.path().join("old-v4.omni");
200+
let schema = fixture("search.pg");
201+
let data = fixture("search.jsonl");
202+
let old_uri = old_graph.to_str().unwrap();
203+
204+
assert_ok(
205+
"v4 init",
206+
&run_old(
207+
&previous,
208+
&["init", "--schema", schema.to_str().unwrap(), old_uri],
209+
),
210+
);
211+
assert_ok(
212+
"v4 load",
213+
&run_old(
214+
&previous,
215+
&[
216+
"load",
217+
"--mode",
218+
"overwrite",
219+
"--data",
220+
data.to_str().unwrap(),
221+
old_uri,
222+
],
223+
),
224+
);
225+
assert!(
226+
!old_graph.join("_graph_commits.lance").exists(),
227+
"a genuine v4 graph keeps graph lineage inside __manifest",
228+
);
229+
230+
let export = run_old(&previous, &["export", old_uri]);
231+
assert_ok("v4 export", &export);
232+
let jsonl = temp.path().join("v4.jsonl");
233+
std::fs::write(&jsonl, &export.stdout).unwrap();
234+
235+
let refusal = output_failure(cli().arg("snapshot").arg(&old_graph));
236+
let stderr = String::from_utf8_lossy(&refusal.stderr);
237+
assert!(stderr.contains("0.8.x"), "got: {stderr}");
238+
assert!(stderr.contains("export"), "got: {stderr}");
239+
240+
let new_graph = temp.path().join("new-v5.omni");
241+
output_success(
242+
cli()
243+
.arg("init")
244+
.arg("--schema")
245+
.arg(&schema)
246+
.arg(&new_graph),
247+
);
248+
output_success(
249+
cli()
250+
.arg("load")
251+
.arg("--mode")
252+
.arg("overwrite")
253+
.arg("--data")
254+
.arg(&jsonl)
255+
.arg(&new_graph),
256+
);
257+
let reexport = output_success(cli().arg("export").arg(&new_graph));
258+
assert_eq!(
259+
nonblank_lines(&export.stdout),
260+
nonblank_lines(&reexport.stdout)
261+
);
262+
let original_ml_intro = exported_row_with_slug(&export.stdout, "ml-intro");
263+
let rebuilt_ml_intro = exported_row_with_slug(&reexport.stdout, "ml-intro");
264+
assert_eq!(
265+
rebuilt_ml_intro["data"]["embedding"], original_ml_intro["data"]["embedding"],
266+
"v4 → v5 rebuild must preserve vector values, not merely row count"
267+
);
268+
269+
let reverse = run_old(&previous, &["snapshot", new_graph.to_str().unwrap()]);
270+
assert!(
271+
!reverse.status.success(),
272+
"a v4 binary must refuse a genuine v5 graph"
273+
);
274+
let reverse_stderr = String::from_utf8_lossy(&reverse.stderr);
275+
assert!(
276+
reverse_stderr.contains("upgrade omnigraph")
277+
|| reverse_stderr.contains("newer")
278+
|| reverse_stderr.contains("expects v4"),
279+
"unexpected reverse-refusal message: {reverse_stderr}",
280+
);
281+
}

crates/omnigraph-compiler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ serde = { workspace = true }
2222
serde_json = { workspace = true }
2323
ahash = { workspace = true }
2424
sha2 = { workspace = true }
25+
ulid = { workspace = true }

0 commit comments

Comments
 (0)