Skip to content

Name the writing release in the storage-format upgrade refusal#315

Open
ragnorc wants to merge 6 commits into
mainfrom
audit-versioning-practices
Open

Name the writing release in the storage-format upgrade refusal#315
ragnorc wants to merge 6 commits into
mainfrom
audit-versioning-practices

Conversation

@ragnorc

@ragnorc ragnorc commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What & why

The storage-format upgrade refusal told operators to export with "the older omnigraph binary that created it" without saying which release — leaving them to guess, and stuck if the fleet was already upgraded. This names the exact release line that wrote each internal-schema stamp (e.g. v3 → 0.6.2–0.7.2) plus the precise export/init/load commands, keeping the strand-model upgrade fail-closed but self-service. The message is engine-shared, so server boot-quarantine logs and cluster status observations get the better text for free.

Backing issue / RFC

  • Trivial fast-lane — a focused UX/docs improvement to the already-documented export/import upgrade path; no architecture change and the strand model is untouched. (Maintainer internal process applies.)

Checklist

  • Change is focused (one logical change)
  • Tests added/updated for behavior changes — in-source refusal assertion (red→green) + a gated genuine-v3 cross-version round-trip test
  • Public docs updated — upgrade.md (release-named message + stamp→release table), versioning.md, testing.md
  • Reviewed against docs/dev/invariants.md — no Hard Invariant weakened; the refusal is still fail-closed in both directions; in-engine read of old formats was deliberately NOT added

Notes for reviewers

  • Stamp→release map is derived from the release tags (verified: v3 starts at 0.6.2, not 0.7.0).
  • crates/omnigraph-cli/tests/crossversion_upgrade.rs mints a real v3 graph with omnigraph 0.7.2 and is gated on OMNIGRAPH_OLD_BIN; it skips in the default suite and runs in the new gated crossversion_upgrade CI job (installs omnigraph-cli@0.7.2). It is the empirical proof the existing stamp-rewind test can't give.

Note

Low Risk
Messaging and documentation only on an already fail-closed open path; no change to migration policy or in-place upgrades. Residual risk is limited to incorrect stamp→release mapping in operator-facing text.

Overview
When a graph’s internal-schema stamp is below what the current binary serves, the refusal message is more actionable: it names the release line that wrote that stamp (via new release_for_internal_schema_version in migrations.rs) and spells out the full exportinitload rebuild steps instead of telling operators to use “the older binary” with no version hint. Open logic and the strict single-version strand model are unchanged—still fail-closed.

Tests and CI: In-source checks assert the named release appears in refusals; crossversion_upgrade.rs (gated on OMNIGRAPH_OLD_BIN) builds a genuine v3 graph with omnigraph 0.7.2 and verifies refusal text plus export round-trip including vectors. A new crossversion_upgrade job and run_upgrade_ci path filter run that test on PRs that touch the upgrade surface.

Docs: upgrade.md, versioning.md, and testing.md document the new message, stamp→release table, and how to run the gated test locally and in CI.

Reviewed by Cursor Bugbot for commit e79b1cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Greptile Summary

This PR improves the storage-format upgrade refusal message by naming the exact omnigraph release line that wrote each internal-schema stamp, so operators know which old binary to fetch without guessing. The fail-closed open behaviour and the stamp check logic are untouched — only the error text, tests, docs, and CI wiring change.

  • release_for_internal_schema_version is added to migrations.rs and wired into refuse_if_stamp_unsupported, mapping stamps 1–4 to their release ranges and adding in-source unit coverage.
  • crossversion_upgrade.rs is a new gated integration test that mints a genuine v3 graph with omnigraph 0.7.2 (not a stamp-rewind stand-in) and verifies both the release-named refusal and the full export → init → load round-trip; CI gains a matching crossversion_upgrade job with path-filtered triggering.
  • upgrade.md, versioning.md, and testing.md are updated with the new error shape, a stamp→release table, and local run instructions for the gated test.

Confidence Score: 5/5

Safe to merge — only the refusal error text, tests, docs, and CI path filter change; the stamp-check logic and fail-closed open contract are untouched.

The change is purely additive: a new pure function maps stamps to release strings, the error message format string is updated to embed it, and the new cross-version CI job exercises a genuine v3 graph. No data path, no migration logic, and no invariant is altered.

No files require special attention. Previously flagged gaps (missing v4 row in the upgrade table, fallback wording) are already noted in open review threads.

Important Files Changed

Filename Overview
crates/omnigraph/src/db/manifest/migrations.rs Adds release_for_internal_schema_version mapping stamps 1–4 to release ranges; wires it into refuse_if_stamp_unsupported; adds unit tests. Logic is correct and the _ fallback is unreachable for real on-disk v1–v3 graphs.
crates/omnigraph-cli/tests/crossversion_upgrade.rs New gated integration test; guards against vacuous skip when OMNIGRAPH_OLD_BIN is set but stale, checks full range string "0.6.2 to 0.7.2", and verifies the export → init → load round-trip. Assertions align with the in-source test in tests.rs.
crates/omnigraph/src/db/manifest/tests.rs Adds msg.contains("0.7.2") assertion to the existing refusal test, binding the test to the newly named release line.
.github/workflows/ci.yml Adds run_upgrade_ci path filter (covering migrations.rs, tests.rs, crossversion_upgrade.rs, export/loader paths, and upgrade.md) and a new crossversion_upgrade job that installs omnigraph-cli@0.7.2 via cargo install --locked and runs the gated test.
docs/user/operations/upgrade.md Updates the error-message example and adds a stamp→release table for v1–v3; v4 ("0.8.x") is absent from the table even though release_for_internal_schema_version maps it — already flagged in a previous review thread.
docs/dev/versioning.md Adds a short paragraph documenting the named-refusal behaviour and its self-service intent.
docs/dev/testing.md Adds a "Cross-version upgrade" section describing the gated test, how to run it locally, and what it proves beyond the stamp-rewind stand-in.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Open graph] --> B[read_stamp from __manifest]
    B --> C{stamp > CURRENT?}
    C -- Yes --> D["Error: upgrade omnigraph\n(no release lookup)"]
    C -- No --> E{stamp < MIN_SUPPORTED?}
    E -- No --> F[Accept — serve graph]
    E -- Yes --> G[release_for_internal_schema_version]
    G --> H{"stamp match"}
    H -- 1 --> I["0.3.1 or earlier"]
    H -- 2 --> J["0.4.1 to 0.6.1"]
    H -- 3 --> K["0.6.2 to 0.7.2"]
    H -- 4 --> L["0.8.x"]
    H -- _ --> M["an unrecognized older release"]
    I & J & K & L & M --> N["Error: named-release refusal\n+ exact export/init/load commands\n+ link to upgrade.md"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Open graph] --> B[read_stamp from __manifest]
    B --> C{stamp > CURRENT?}
    C -- Yes --> D["Error: upgrade omnigraph\n(no release lookup)"]
    C -- No --> E{stamp < MIN_SUPPORTED?}
    E -- No --> F[Accept — serve graph]
    E -- Yes --> G[release_for_internal_schema_version]
    G --> H{"stamp match"}
    H -- 1 --> I["0.3.1 or earlier"]
    H -- 2 --> J["0.4.1 to 0.6.1"]
    H -- 3 --> K["0.6.2 to 0.7.2"]
    H -- 4 --> L["0.8.x"]
    H -- _ --> M["an unrecognized older release"]
    I & J & K & L & M --> N["Error: named-release refusal\n+ exact export/init/load commands\n+ link to upgrade.md"]
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into audit-versionin..." | Re-trigger Greptile

ragnorc added 3 commits June 29, 2026 18:45
Two tests for the storage-format upgrade message, landed before the fix so the
red->green pair is visible in git log:

- Extend sub_current_graph_is_refused_then_rebuilt_via_export_import to assert the
  open-refusal NAMES the release that wrote the stamp (v3 -> 0.7.2), not just that
  it nudges to `export`. Red against the current message.
- Add crossversion_upgrade.rs: mints a GENUINE internal-schema-v3 graph with an
  omnigraph 0.7.2 binary (real _graph_commits.lance, lineage-free __manifest) via
  OMNIGRAPH_OLD_BIN, then asserts the current binary refuses it with the
  release-named message and that export -> init -> load round-trips (row count +
  Vector column). Gated on OMNIGRAPH_OLD_BIN; skips when unset so the default
  suite stays green. The stamp-rewind stand-in cannot prove cross-format decode.
The strand-model upgrade requires exporting an old graph with the binary that
wrote it, but the open-refusal only said "the older omnigraph binary that created
it" -- leaving the operator to guess which release, and stuck if the fleet is
already upgraded.

Add release_for_internal_schema_version (v1 <=0.3.1, v2 0.4.1-0.6.1, v3
0.6.2-0.7.2, v4 0.8.x -- derived from the release tags that stamped each version)
and embed it plus the exact export/init/load commands in the below-CURRENT
refusal. The upgrade stays fail-closed (the graph is still refused) and becomes
self-service. Engine-shared, so server boot-quarantine logs and cluster status
observations carry the better message too. Turns the prior commit's tests green.
- upgrade.md: show the release-named refusal and add a "which old binary do I
  need?" stamp->release table.
- versioning.md: note the below-CURRENT refusal names the writing release, so the
  upgrade is fail-closed AND self-service.
- testing.md: document crossversion_upgrade.rs and the gated CI job.
- ci.yml: add a run_upgrade_ci classifier output and a crossversion_upgrade job
  that installs omnigraph-cli@0.7.2 and runs the gated genuine-v3 test.
Comment thread docs/user/operations/upgrade.md
Comment thread crates/omnigraph-cli/tests/crossversion_upgrade.rs
Comment thread crates/omnigraph/src/db/manifest/migrations.rs Outdated
Comment thread .github/workflows/ci.yml

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84449856f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +26 to +28
fn old_bin() -> Option<PathBuf> {
let path = PathBuf::from(std::env::var_os("OMNIGRAPH_OLD_BIN")?);
path.exists().then_some(path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail when configured old binary is missing

When OMNIGRAPH_OLD_BIN is set but points at a missing path, old_bin() returns None, so the test takes the same skip path as an unset variable. In the new CI job this can make a misconfigured install path or changed binary name pass vacuously without minting a v3 graph at all; only the env-var-absent case should skip, while a configured-but-missing path should fail loudly.

Useful? React with 👍 / 👎.

ragnorc and others added 2 commits June 30, 2026 08:05
Address P2 review feedback on #315:

- crossversion_upgrade.rs: OMNIGRAPH_OLD_BIN set-but-missing now fails loudly
  instead of skipping vacuously (only an UNSET var is a legitimate skip) — a
  misconfigured CI install path no longer passes without minting a v3 graph.
- crossversion_upgrade.rs: tighten the refusal assertion from `0.7` to the full
  `0.6.2 to 0.7.2` range, matching its in-source sibling.
- ci.yml: add db/manifest/tests.rs to the run_upgrade_ci path filter so a future
  edit to the in-source refusal assertion still triggers the cross-version job.
- migrations.rs: reword the unreachable release fallback to read naturally after
  "created by omnigraph " (defensive only; 1-3 are mapped, >=CURRENT is caught
  by the ceiling guard first).

Declined: adding a v4 row to the upgrade.md stamp->release table — that table is
for sub-CURRENT (refused) stamps only, and a v4 graph under a v4 binary is never
refused, so a v4 row would wrongly imply a current graph needs rebuilding.
@ragnorc

ragnorc commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed in 817979e1:

  • old_bin() vacuous skip (Codex): a set-but-missing OMNIGRAPH_OLD_BIN now fails loudly instead of skipping; only an unset var is a legitimate skip, so a misconfigured CI install path can't pass without minting a v3 graph.
  • Loose cross-version assertion (Greptile): tightened "0.7" → the full "0.6.2 to 0.7.2" range, matching its in-source sibling.
  • tests.rs not in the CI filter (Greptile): added crates/omnigraph/src/db/manifest/tests.rs to run_upgrade_ci so a future edit to the in-source refusal assertion still triggers the cross-version job.
  • Awkward fallback grammar (Greptile): reworded the (unreachable) _ arm to read naturally after "created by omnigraph ".

Declined: the v4 row in the upgrade.md stamp→release table. That table answers "which old binary do I need?" for sub-CURRENT stamps that get refused. A v4 graph under a v4 binary is never refused — it opens normally — so a v4 row would wrongly imply a current graph needs rebuilding. v4 joins the table when v5 ships and the doc is revised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant