diff --git a/crates/omnigraph-cli/tests/crossversion_upgrade.rs b/crates/omnigraph-cli/tests/crossversion_upgrade.rs index 4edc95bb..b3e45f5c 100644 --- a/crates/omnigraph-cli/tests/crossversion_upgrade.rs +++ b/crates/omnigraph-cli/tests/crossversion_upgrade.rs @@ -538,8 +538,11 @@ fn current_v9_refuses_and_rebuilds_genuine_v5_and_v5_refuses_v9() { let refusal = output_failure(cli().arg("snapshot").arg(&v5_graph)); let stderr = String::from_utf8_lossy(&refusal.stderr); assert!( - stderr.contains("0.9.x"), - "v5 refusal must name the release line that wrote internal schema v5, got: {stderr}", + stderr.contains("0.9.0-dev"), + "v5 refusal must name the build that wrote internal schema v5. No published \ + release ever stamped it — it existed only in source builds inside the \ + 0.8.1 -> 0.9.0 development window — so the refusal names `0.9.0-dev`, \ + got: {stderr}", ); assert!( stderr.contains("export"), @@ -681,8 +684,11 @@ fn current_v9_refuses_and_rebuilds_genuine_v6_and_v6_refuses_v9() { let refusal = output_failure(cli().arg("snapshot").arg(&v6_graph)); let stderr = String::from_utf8_lossy(&refusal.stderr); assert!( - stderr.contains("0.10.x"), - "v6 refusal must name the release line that wrote internal schema v6, got: {stderr}", + stderr.contains("0.9.0-dev"), + "v6 refusal must name the build that wrote internal schema v6. No published \ + release ever stamped it — it existed only in source builds inside the \ + 0.8.1 -> 0.9.0 development window — so the refusal names `0.9.0-dev`, \ + got: {stderr}", ); assert!( stderr.contains("export"), @@ -771,8 +777,11 @@ fn current_v9_refuses_and_rebuilds_genuine_v7_and_v7_refuses_v9() { let refusal = output_failure(cli().arg("snapshot").arg(&v7_graph)); let stderr = String::from_utf8_lossy(&refusal.stderr); assert!( - stderr.contains("0.11.x"), - "v7 refusal must name the release line that wrote internal schema v7, got: {stderr}", + stderr.contains("0.9.0-dev"), + "v7 refusal must name the build that wrote internal schema v7. No published \ + release ever stamped it — it existed only in source builds inside the \ + 0.8.1 -> 0.9.0 development window — so the refusal names `0.9.0-dev`, \ + got: {stderr}", ); assert!( stderr.contains("export"), @@ -888,8 +897,11 @@ fn current_v9_refuses_and_rebuilds_genuine_v8_and_v8_refuses_v9() { let refusal = output_failure(cli().arg("snapshot").arg(&v8_graph)); let stderr = String::from_utf8_lossy(&refusal.stderr); assert!( - stderr.contains("0.12.x"), - "v8 refusal must name the release line that wrote internal schema v8, got: {stderr}", + stderr.contains("0.9.0-dev"), + "v8 refusal must name the build that wrote internal schema v8. No published \ + release ever stamped it — it existed only in source builds inside the \ + 0.8.1 -> 0.9.0 development window — so the refusal names `0.9.0-dev`, \ + got: {stderr}", ); assert!( stderr.contains("export"), diff --git a/docs/dev/testing.md b/docs/dev/testing.md index dd068cfb..90b90edf 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -585,7 +585,7 @@ table; the v4 case also pins reverse refusal by the old binary. RFC-023 added its then-immediate-predecessor case gated on `OMNIGRAPH_V5_BIN`, built from the final internal-v5 commit. It mints a genuine SchemaIR-v2 v5 graph, -proves v9 refuses it with the 0.9.x rebuild guidance, exports with v5, rebuilds +proves v9 refuses it with the `0.9.0-dev` rebuild guidance, exports with v5, rebuilds under v9, checks row/vector/blob fidelity, exact blob bytes, and exact-`id` PK metadata, then proves the v5 binary refuses the v9 root. The same cell injects a duplicate logical ID into the v5 export: v9 rejects the load atomically, @@ -615,8 +615,10 @@ OMNIGRAPH_V7_BIN=/path/to/final-v7/omnigraph \ ``` RFC-026 Phase B2 adds the immediate-predecessor `OMNIGRAPH_V8_BIN` seam. It -mints a genuine internal-v8/config-v2 graph, proves v9 refuses it with 0.12.x -rebuild guidance, exports with v8, rebuilds a distinct v9/config-v3 root, and +mints a genuine internal-v8/config-v2 graph, proves v9 refuses it with +`0.9.0-dev` rebuild guidance (no published release ever stamped v5–v8, so the +refusal names the development window rather than a release line), exports with +v8, rebuilds a distinct v9/config-v3 root, and proves row/vector fidelity plus exact-`id` PK metadata. The v9 re-export must not expose the physical `__omnigraph_stream_v1$` attribution column, and the v8 fixture's ordinary user property `__omnigraph_stream_v1` must retain its value; diff --git a/docs/dev/versioning.md b/docs/dev/versioning.md index f6f0dcbc..4b45a86d 100644 --- a/docs/dev/versioning.md +++ b/docs/dev/versioning.md @@ -38,14 +38,17 @@ together. A v4 graph cannot be backfilled safely because its logical IDs, registration keys, paths, versions, and tombstones are all name-derived; the normal strand rebuild mints a fresh domain and table incarnations instead. -Internal schema v6 mapped to OmniGraph 0.10.x. It preserved the v5 identity +Internal schema v6 was a 0.9.0-dev format: like v5, v7, and v8 it was written +only by source builds off `main` during 0.9.0 development, and no published +release serves it. It preserved the v5 identity contract and activated RFC-023 key fencing: every graph node/edge dataset declares exactly non-null physical `id` as Lance's unenforced primary key from creation, and production strict insert/upsert routes use the exact-`id` filter-bearing adapter. Internal schema **v9 is the currently served format** and maps to OmniGraph -**0.13.x**. It preserves v8's private data-bearing MemWAL core, then activates +**0.9.x** — the first published release line to serve any of these formats. +It preserves v8's private data-bearing MemWAL core, then activates RFC-026's common B2 storage/recovery contract: stream-config v3, lifecycle state v2, the grammar-impossible trusted base-row field `__omnigraph_stream_v1$`, one manifest-selected `_stream_tokens.lance` @@ -62,7 +65,7 @@ canonical payloads, token projections, recovery JSON, and exact-authority lookup retention are bounded; the measured near-cap fold RSS remains evidence, not a runtime allocator promise. -A v8/0.12.x graph is not reinterpreted or migrated in place: config-v2 and +A v8 (0.9.0-dev) graph is not reinterpreted or migrated in place: config-v2 and recovery-v11 never become config-v3/state-v2/recovery-v12 authority. Export it with the v8 binary, initialize a different v9 root, and load through the v9 writer. The physical field's trailing `$` is outside the `.pg` identifier diff --git a/docs/dev/writes.md b/docs/dev/writes.md index 91baa24c..f4c39016 100644 --- a/docs/dev/writes.md +++ b/docs/dev/writes.md @@ -1277,13 +1277,14 @@ strand model): this binary reads exactly ONE internal-schema version policy) and [the upgrade guide](../user/operations/upgrade.md) (the rebuild recipe). -V7 maps to the 0.11.x release line. It preserves v5 stable identity and v6 +V7 is a 0.9.0-dev format (no published release serves it). It preserves v5 +stable identity and v6 exact-`id` key fencing, and adds the RFC-026 Phase A lifecycle/enrollment foundation described above. A genuine v6 root moves to v7 only through export/init/load into a different root; v7 refuses v6 and a v6 binary refuses v7. -V8 maps to the 0.12.x release line. It preserves the v7 foundation and activates +V8 is likewise a 0.9.0-dev format. It preserves the v7 foundation and activates the private RFC-026 B1 stream-config-v2 row/fold core plus recovery-v11. A v7 root moves to v8 only through export/init/load into a different root; v8 refuses v7 and a v7 binary refuses v8. This format capability does not expose a public diff --git a/docs/rfcs/0023-key-conflict-fencing.md b/docs/rfcs/0023-key-conflict-fencing.md index 8b8c3bd9..4842bcda 100644 --- a/docs/rfcs/0023-key-conflict-fencing.md +++ b/docs/rfcs/0023-key-conflict-fencing.md @@ -20,7 +20,8 @@ conflict-resolver and merge-insert sources plus runtime surface probes **Evidence status (2026-07-17):** the beta.21 filter shape and directional conflict matrix were revalidated unchanged by the RC.1 surface guards. The historical performance measurements below remain labeled beta.21. The implementation now activates -the contract in internal schema v6 (the OmniGraph 0.10.x format): fresh +the contract in internal schema v6 (a 0.9.0-dev format; no published release +served it): fresh node/edge datasets carry exact-`id` PK metadata from creation, all production graph insert/upsert routes use the sealed filter-bearing keyed adapter, bare keyed Append is source-guarded out of production, and effect-free conflicts @@ -571,8 +572,9 @@ compatibility check. A fencing-capable binary keeps instructions, and refuses a newer graph with “upgrade omnigraph.” An older binary refuses the fencing-capable stamp. -The fencing-compatible format is internal schema **v6**, mapped to OmniGraph -**0.10.x**. It follows RFC-028's v5 identity format; RFC-024 and later draft +The fencing-compatible format is internal schema **v6**, a 0.9.0-dev format +(the planned 0.10.x mapping was superseded when v6–v8 stayed unreleased and +0.9.0 shipped v9). It follows RFC-028's v5 identity format; RFC-024 and later draft capabilities are not included. Each later internal-format change requires its own rebuild under the strand policy unless independently accepted capabilities deliberately co-release after a combined initialization and recovery review. @@ -1264,8 +1266,9 @@ correctness cells are recorded in §11.2 and are green. 5. **Retained support boundary, not a current-topology acceptance gate:** cross-process recovery ownership must land before any broadened topology claim. V6 destructive recovery remains single-writer-process. -6. **Satisfied 2026-07-15:** internal schema v6 is the fencing format for - OmniGraph 0.10.x, and a genuine final-v5 binary passed v6 refusal, +6. **Satisfied 2026-07-15:** internal schema v6 is the fencing format (then + planned for 0.10.x; ultimately a 0.9.0-dev format), and a genuine final-v5 + binary passed v6 refusal, export/init/load with row/vector/blob fidelity, exact blob bytes, and exact-`id` target PKs; duplicate input failed atomically with an empty target and unchanged source; v5 also refused the rebuilt v6 root. Branch fork, diff --git a/docs/user/operations/upgrade.md b/docs/user/operations/upgrade.md index ef154776..6fa4154b 100644 --- a/docs/user/operations/upgrade.md +++ b/docs/user/operations/upgrade.md @@ -264,11 +264,12 @@ merge-insert builder has no `WriteParams` hook, while Overwrite does. This format cannot be obtained by adding metadata to a live v5 root. Lance's filtered/unfiltered conflict behavior is directional, so every table image and -every writer must cross the boundary together. Quiesce writers, export with the -latest 0.9.x binary, initialize a **different** root with the 0.10.x binary, -load the export, verify the v6 stamp and data, then cut the whole fleet over. -The 0.10.x binary refuses the v5 source root, and the 0.9.x binary refuses the -new v6 root. +every writer must cross the boundary together. Both v5 and v6 are 0.9.0-dev +formats with no published binary, so this crossing uses source builds: quiesce +writers, export with a source build at the final internal-v5 commit, +initialize a **different** root with a final internal-v6 source build, load +the export, verify the v6 stamp and data, then cut the whole fleet over. The +v6 build refuses the v5 source root, and the v5 build refuses the new v6 root. The v6 load checks the export for duplicate logical IDs before any table effect. Older bare-Append workloads could contain a committed collision; do not resolve @@ -630,12 +631,13 @@ It does **not** expose streaming ingestion. There is no `@stream`, production enrollment command or API, WAL row acknowledgement, fold, drain/resume, or fresh-read surface in this format slice. -Move a v6 graph to v7 with the ordinary recipe at the top of this page: export -with the latest 0.10.x binary, initialize a **different** root with the 0.11.x -binary, load the export, verify the v7 stamp and logical data, and cut the whole -fleet over together. The 0.11.x binary refuses the v6 source root, and the -0.10.x binary refuses the new v7 root. Genuine cross-version tests pin both -directions. +Move a v6 graph to v7 with the ordinary recipe at the top of this page — both +are 0.9.0-dev formats, so the crossing uses source builds: export with a final +internal-v6 source build, initialize a **different** root with a final +internal-v7 source build, load the export, verify the v7 stamp and logical +data, and cut the whole fleet over together. The v7 build refuses the v6 +source root, and the v6 build refuses the new v7 root. Genuine cross-version +tests pin both directions. V6 has no acknowledged MemWAL rows, so there is no stream backlog to drain before this particular rebuild. Export transfers only manifest-visible logical @@ -653,9 +655,10 @@ and schema-v11 `StreamFold` recovery through the unified write path. It still does **not** expose public stream schema, SDK, HTTP, CLI, or operator controls; those remain Phase B2 gates. -Move a v7 graph to v8 with the ordinary recipe at the top of this page: export -with the latest 0.11.x binary, initialize a different root with the 0.12.x -binary, load the export, and verify the v8 stamp and logical data. V7's +Move a v7 graph to v8 with the ordinary recipe at the top of this page — both +are 0.9.0-dev formats, so the crossing uses source builds: export with a final +internal-v7 source build, initialize a different root with a final internal-v8 +source build, load the export, and verify the v8 stamp and logical data. V7's config-v1 enrollment is never reinterpreted as data-bearing config-v2 state. The rebuild copies manifest-visible rows only, not MemWAL indexes, shard state, recovery intents, or epochs, so the new root starts unenrolled.