Skip to main content

velesdb_memory/
migration.rs

1//! Read-only diagnosis of a store that a changed embedding model has made
2//! unopenable, and the feasibility proof the rebuild depends on (#1762, PR A).
3//!
4//! # What this module is NOT
5//!
6//! It does not migrate, does not switch anything over, and never writes to the
7//! store it inspects. Producing a [`crate::migration::MigrationState`] is a
8//! later step behind an explicit prepare command; a diagnosis yields a
9//! [`crate::migration::DiagnosisReport`] and nothing else.
10//!
11//! # Why a feasibility proof comes first
12//!
13//! A rebuild must re-insert every fact under its ORIGINAL `u64` id: edges are
14//! `(id, from, to, relation)` with no vector of their own, entity hubs derive
15//! their id from the topic, and the working-context index addresses facts by
16//! id. Renumbering would silently sever all three. So before any rebuild code
17//! is written, the architecture has to be shown to support reading every fact
18//! back out — ids, content, ordinary metadata, RESERVED metadata and the
19//! absolute expiry — and putting it back unchanged.
20//!
21//! `MemoryStore` offers no enumeration at all: every read is by id or a
22//! top-`k` vector search, and `count()` counts without listing. Two paths down
23//! into the engine do, and they are not equivalent:
24//!
25//! * a `VelesQL` scan with no vector predicate, walked by `LIMIT`/`OFFSET`
26//!   ([`crate::migration::enumerate_collection`]) — complete and
27//!   deterministic, but quadratic, and BOUNDED: the pipeline clamps
28//!   `limit + offset` to 100_000 and goes silently empty past that mark;
29//! * the collection's own `scroll_batch`
30//!   ([`crate::migration::enumerate_by_cursor`]) — a cursor keyed on the point
31//!   id, exclusive and ascending, which bypasses the query pipeline and so
32//!   carries neither the clamp nor the re-sort.
33//!
34//! The first was written first because `WHERE id > n` genuinely does not work —
35//! filters read the payload and the id is not in it. That ruled out expressing
36//! a cursor *in `VelesQL`*; it did not rule out the cursor, and treating the
37//! query language's limit as the architecture's limit is the error this module
38//! now records rather than repeats.
39//!
40//! That either *parses* is not the proof. Both are measured by running them
41//! against a seeded store and comparing what comes back, field by field, and
42//! against each other.
43
44// The `persistence` gate lives on the `pub mod migration;` declaration in
45// `lib.rs`; repeating it here as an inner attribute is what `clippy::
46// duplicated_attributes` fires on.
47
48mod cli;
49mod diagnosis;
50mod diagnostic_copy;
51mod edges;
52mod enumeration;
53mod execute;
54mod filesystem;
55mod orchestrate;
56mod rebuild;
57mod state;
58mod strategy;
59mod switchover;
60mod validate;
61
62pub use cli::{
63    default_scratch_parent, dry_run, migration_complete_notice, parse as parse_migrate_args,
64    refuses, render, require_destination, MigrateOptions,
65};
66pub use diagnosis::{
67    diagnose, same_filesystem, Capability, CollectionInventory, DiagnosisReport, SourceProvenance,
68    TargetContract, TtlSummary, DIAGNOSIS_FORMAT_VERSION,
69};
70pub use edges::{
71    cross_check_edges, export_edges, export_edges_verified, reinsert_edges, EdgeReinsertion,
72};
73pub use enumeration::{
74    enumerate_by_cursor, enumerate_collection, enumerate_page, reinsert, reinsert_batch,
75    scroll_page, BatchReinsertion, RawFact, Reinsertion, AGENT_COLLECTIONS,
76};
77pub use execute::{execute, ExecuteOutcome};
78pub use filesystem::{bytes_on_disk, fingerprint};
79pub use orchestrate::{migrate, MigrateOutcome};
80#[cfg(test)]
81pub(crate) use rebuild::rebuild_with_stop;
82pub use rebuild::{
83    rebuild, RebuildDestination, RebuildJournal, RebuildOutcome, RebuildSource, VectorPolicy,
84};
85pub use state::{
86    CollectionProgress, MigrationLock, MigrationState, Phase, Recovery, SwitchState, LOCK_FILE,
87    PHASES, STATE_FILE, STATE_FORMAT_VERSION, STATE_TEMP_FILE,
88};
89pub use strategy::{assess, resolve, Compatibility, Resolution, Strategy};
90pub use switchover::{switch_over, SwitchOutcome, ARCHIVE_SUFFIX};
91#[cfg(test)]
92pub(crate) use validate::divergence_explained_by_expiry;
93pub use validate::{validate_destination, ValidationOutcome};
94
95/// The one conversion every migration module needs: a message become the
96/// engine's query error, become this crate's. Defined once — six private
97/// copies of it had already drifted into two signatures.
98pub(crate) fn query_error(message: impl Into<String>) -> crate::MemoryError {
99    velesdb_core::Error::Query(message.into()).into()
100}
101
102#[cfg(test)]
103mod tests;