Repo-wide rules are in the root AGENTS.md. This crate answers nearest-neighbour
queries over every VECTOR(n) column: one index per (table, column), split into partition
layouts, each served from a sealed base graph, sealed change graphs and a mutable tail under one
MVCC snapshot.
Owns: the index registry and partition layouts (store.rs: VectorStore, IndexState,
VectorIndexLayout, ResolvedVectorPolicy); exact, filtered and indexed search and the global
top-k merge; the in-memory generation lifecycle (register, preload, evict, quarantine, publish a
replacement, reclaim once no snapshot needs it); one partition's maintenance step
(VectorStore::run_hnsw_maintenance_cycle); the graph wrapper and its owned generation bytes
(hnsw.rs: HnswIndex); the one unfiltered F32 traversal that serves both a resident graph and
a saved graph read page by page (hnsw/unfiltered.rs) and the multi-partition union search
(hnsw/union.rs); SQ8/SQ4 payloads (quantized.rs); memory charge ownership
(memory_budget.rs); the core VectorExecutor implementation (mem.rs: MemVectorExecutor).
Must not own: where generation bytes, raw vectors, the catalog or the change journal are stored,
or when a maintenance cycle runs — the engine does both. SQL grammar, planning, row
authorization and sync belong to the parser, planner and engine. A second graph implementation
never goes here or anywhere: graph algorithms come from contextdb-hnsw, and anything new that
vector search needs from a graph is written here, in hnsw.rs, on top of that fork.
- Above:
contextdb-engineowns aVectorStore, drives maintenance fromDatabase::run_maintenance_cycle, and implementsDormantVectorGraphLoaderandDormantRawVectorLoader(database.rs) so a partition loads only when a query or a maintenance step needs it. - Below:
contextdb-hnsw(library namehnsw_rs) for graphs;contextdb-coreforVectorExecutor,VectorIndexRef,VectorEntry,Error;contextdb-txfor snapshots andWriteSetApplicator. - Test seams: pause, fault and observation hooks are
#[doc(hidden)], behind thetest-seamsfeature (test_seam.rs, the*ForTesttypes re-exported fromlib.rs). Production code never reads them.
| Invariant | Guard (file → test) |
|---|---|
Search on one (table, column) never returns another column's vectors |
tests/vector_tests.rs → search_on_ref_a_returns_only_ref_a_vectors_under_10000_iteration_apply_storm |
| Equal scores order by ascending row id on the exact and the graph path | tests/vector_tests.rs → public_search_ties_order_by_row_id_in_bruteforce_and_hnsw_paths |
| A snapshot sees exactly the vectors committed at it | tests/vector_tests.rs → mvcc_snapshot_isolation; src/store/transaction_visibility_tests.rs |
| Concurrent updates and deletes leave one live vector and no graph ghosts | tests/vector_tests.rs → concurrent_same_ref_updates_leave_single_live_vector_after_commit, same_ref_concurrent_deletes_leave_no_zombie_hnsw_entries |
| A wrong dimension is a typed error for the named index | tests/vector_tests.rs → dimension_mismatch_typed_error_with_positive_control |
| Building one index never blocks search or ingest on another | tests/vector_tests.rs → index_b_search_completes_while_index_a_hnsw_build_paused, reindex_on_index_a_does_not_block_ingest_on_index_b |
| Retention pruning in selected columns leaves an unrelated column's resident generation, graph and indexed answers intact | tests/vector_tests.rs → selected_index_row_pruning_preserves_unrelated_resident_generation_and_indexed_answers |
| Charged bytes balance: a refusal on one index leaves every other index's charge exact | tests/vector_tests.rs → memory_accountant_rejection_on_a_leaves_b_accountant_byte_balance_correct, clear_hnsw_keeps_accounting_charged_while_search_holds_graph |
| A generation with no live vectors loads as an empty graph | tests/vector_tests.rs → a_durable_generation_with_no_live_vectors_loads_as_an_empty_graph |
| Corrupt or incompatible generation bytes are refused, never searched | src/hnsw.rs → tests::durable_generation_refuses_corruption_and_incompatible_column_definition |
| A saved graph read page by page returns the same neighbours, order and ties, and charges the same distance work and refusals, as the same graph resident in memory | src/hnsw.rs → tests::saved_graph_pages_preserve_owned_neighbor_ids_and_order_across_search_breadths, tests::saved_graph_pages_charge_the_same_distance_expansions_as_owned_graphs, tests::saved_graph_pages_refuse_the_same_distance_budget_as_owned_graphs, tests::saved_graph_pages_return_the_exact_lowest_row_ids_for_equal_vectors |
| A saved base plus resident tail preserves row-id ties even at search breadth one | src/hnsw.rs → tests::mixed_saved_base_and_resident_tail_keep_exact_row_id_ties_at_breadth_one |
| Exhaustive AUTO can stream a dormant partition one body at a time when bulk loading is refused; unrestricted explicit EXACT on a writer still requires that load | crates/contextdb-engine/tests/vector/vector_lazy_raw_residency_contract.rs → auto_exhaustive_find_answers_inside_memory_that_cannot_hold_a_partition |
| Unfiltered search descends each upper level until no closer neighbour remains before searching the bottom level | src/hnsw.rs → tests::unfiltered_upper_descent_finishes_each_level_before_searching_bottom |
A multi-partition search stays within its one statement work budget (max(EF_SEARCH, k) × stored bottom-level degree) and completes its answer; a distance already computed while admitting partitions is reused rather than recomputed, without changing any answer |
crates/contextdb-engine/tests/vector/multi_partition_union_recovers_nearest_rows.rs → uniform_three_and_eight_partition_shared_walk_regression, clustered_one_three_and_eight_partition_shared_walk_regression |
| Filtered search charges no work for excluded history | src/hnsw.rs → tests::ordinary_allowed_search_excluded_history_adds_no_visit_or_distance_work |
| Recall-at-10 of the indexed route is at least 95% of exact | crates/contextdb-engine/tests/vector/vector_held_out_native_reference.rs → held_out_unfiltered_native_reference_recovers_the_required_neighbors |
Every search, index and consolidation threshold comes from the declared column policy resolved in one place (VectorIndexLayout::resolve_policy / effective_auto_index_at in store.rs); an undeclared value falls back to a documented default there, but a declared value is never silently overridden by a constant elsewhere |
crates/contextdb-engine/tests/vector/vector_policy_resolver_contract.rs → declared_vector_policy_resolves_consistently_at_default_and_declared_boundaries |
| Default HNSW breadth and topology follow the live partition size and quantization; partial declarations remain valid across profile boundaries | src/store.rs → tests::compatibility_profile_edges_and_partial_policy_validation_agree |
| Scalar publications preserve every MVCC membership and interval while unchanged raw bodies share one allocation, charged through its final holder | src/store/shared_payload_tests.rs::retained_scalar_memberships_share_one_body_and_keep_all_snapshot_intervals, src/store/shared_payload_tests.rs::shared_cross_partition_body_survives_source_removal_and_final_external_holder, src/store/shared_payload_tests.rs::shared_allocation_header_stays_charged_until_final_holder_after_store_drop |
| Received columns prepare directory, shared-header and required copied-move body growth with their own funder even without a store-wide accountant; inserted bodies are caller-admitted beforehand, abort or refusal releases only preparation reservations, and published payload charges last until the final holder drops | src/store/received_publication_accounting_tests.rs::received_publication_prepares_next_insert_and_releases_its_last_holder, src/store/received_publication_accounting_tests.rs::received_columns_keep_distinct_funders_and_legacy_columns_in_a_mixed_batch |
| Detached-copy hydration moves unique uncharged bodies out of shared headers; shared histories and charged or resident sources keep their representation and final-holder charge | src/store/detached_copy_payload_tests.rs::detached_copy_unique_unwrap_moves_the_body_and_preserves_shared_or_charged_owners, src/store/detached_copy_payload_tests.rs::detached_copy_hydration_retains_only_new_singleton_bodies_and_releases_headers, src/store/detached_copy_payload_tests.rs::detached_copy_header_refusal_releases_loader_body_workspace_and_retries, src/store/detached_copy_payload_tests.rs::detached_copy_mixed_history_preserves_bits_and_shared_final_holder_charges, src/store/detached_copy_payload_tests.rs::detached_copy_already_resident_sources_keep_representation_charge_and_funder |
| Resident generation selection copies only frontier-live entries after the optional LSN boundary, preserving representations and history; inconsistent directory metadata refuses before body materialization | src/store/generation_selection_tests.rs::selected_generation_materialization_preserves_all_representations_and_snapshot_intervals, src/store/generation_selection_tests.rs::selected_generation_refuses_inconsistent_metadata_before_body_materialization |
| Borrowed source metadata and scalar marking copy no giant partition key; stale source identities cannot mark a replacement column or partition | tests/borrowed_route_metadata_adapter.rs::giant_source_metadata_and_marking_allocate_no_giant_key_copy_or_encoding, tests/borrowed_route_metadata_adapter.rs::replacement_column_rejects_the_old_state_without_marking_the_new_source, tests/borrowed_route_metadata_adapter.rs::removed_and_recreated_partition_rejects_the_old_state_in_the_same_column |
Publication, restart, repair and partition behaviour end to end are guarded by the engine's
crates/contextdb-engine/tests/vector_*_contract.rs files; the claims they bind are tagged in
docs/architecture.md and docs/query-language.md.
| Change | Where it lands |
|---|---|
| Streaming exhaustive bodies after refused bulk admission | mem.rs::BoundedBruteForceCursor; store.rs::DormantRawVectorLoader tells it whether a committed-image exact read can stream. The engine supplies the loader. |
| A search route, merge order or refusal | store.rs (per-layout search, global merge), mem.rs for the VectorExecutor entry |
| Graph build or search behaviour, the owned generation byte format | hnsw.rs; unfiltered F32 search in hnsw/unfiltered.rs (one walk for resident and saved graphs — never a second copy), multi-partition union in hnsw/union.rs; the fork only for a primitive hnsw.rs cannot build on |
| A quantization format | quantized.rs, plus the resident-byte estimate beside it |
Per-column policy resolution (AUTO_INDEX_AT, HNSW, CONSOLIDATION, limits) |
store.rs (VectorIndexLayout, ResolvedVectorPolicy); the grammar is the parser's |
| Store-size-dependent default search breadth profile | src/store.rs::compatibility_hnsw_profile, used by VectorIndexLayout::resolve_policy; test all size-band boundaries and partial policy validation in src/store.rs::compatibility_profile_edges_and_partial_policy_validation_agree. |
| What one maintenance step does to a partition | store.rs (run_hnsw_maintenance_cycle, maintenance_need) |
| When maintenance runs, what is persisted, how a partition is loaded from disk | Engine crate (database.rs, persistence.rs) |
| Read-only test observations | test_seam.rs, exported from lib.rs under the test feature; search behavior remains in store.rs and hnsw.rs. |
| Transaction-visible vector state | store/transaction_visibility_tests.rs guards overlay behavior; implementation stays in store.rs and mem.rs. |
| Shared raw-body ownership, received-column funders or detached-copy hydration | store.rs owns preparation, adoption and receipts; quantized.rs owns private payload representations and final-drop charges. Public VectorEntry and durable encoding stay owned-Vec based. |
| Resident generation input selection or borrowed maintenance metadata | store.rs::entries_for_partition_generation_at_snapshot and VectorPartitionSourceMaintenanceMetadata; engine owns frontier admission, catalog inventory and durable cleanup. |
Retention's directory visitor borrows partition keys and raw directory identities, and lets the engine admit each selected mutation before copying it; it never loads vector bodies for deletion.
cargo test -p contextdb-vector --features test-seams
cargo test -p contextdb-engine --features test-seams --test vectorMost vector test files compile only with test-seams (#![cfg(feature = "test-seams")] or
per-test gates); without the flag they build and run nothing. cargo test --workspace enables it
through feature unification.
Retention uses prune_row_ids_in_indexes for selected columns, borrows partition states and removes
whole-row reverse-map entries in place; unrelated indexes retain their resident graph state.
Raw bodies have two private owners. Legacy void insertion, loading and moves keep their Vec inline,
adopting only the caller-admitted body bytes and allocating no shared header; their existing
metadata-growth protocol is unchanged. Fallible loaded-state accounting prices bodies,
directories and promotion headers before moving each owned Vec into shared ownership without
cloning it. Existing body charges retain their actual funder; a later open's newly admitted header
retains its own funder through final drop. Refusal preserves representation and caller reservations.
A prepared move from
an owned source admits a distinct shared body and header while retaining the historical source;
later moves from shared sources keep distinct MVCC memberships on one immutable allocation.
Hydration shares only bit-identical stored representations, preserving signed zero and quantized
bounds. Owned bodies count individually; shared bodies deduplicate by Arc allocation identity.
Each allocation keeps its charge through final holder drop, including partition moves, eviction
and registry removal. Receipts include live shared headers in raw_reverse_directory;
raw_vector_bodies retains its payload-byte meaning. Directory memberships remain charged per
version. Public VectorEntry values and durable codecs keep their existing owned-Vec encoding.
Legacy compatibility and promotion ownership are additionally guarded by deterministic tests in
src/store/shared_payload_tests.rs: legacy_void_inserts_adopt_only_funded_bodies_without_shared_header_headroom,
loaded_owned_promotion_refusal_preserves_reservations_then_moves_each_body_without_cloning,
mixed_already_accounted_open_admits_only_new_headers_and_transfers_live_counters,
owned_empty_payloads_promote_to_distinct_shared_allocations,
retained_owned_clone_requires_its_own_body_charge_and_never_copies_a_charge_token,
prepared_owned_source_copy_is_admitted_and_mixed_memberships_prune_exactly,
mixed_owned_shared_eviction_and_reload_preserve_final_holder_charges, and
late_legacy_index_inherits_only_the_callers_funded_body_owner,
promotion_keeps_legacy_body_and_new_header_with_their_actual_funders, and
already_accounted_empty_state_hydration_attaches_every_new_shared_charge, and
legacy_shared_source_move_retains_its_funded_owned_copy_without_new_header.
Received publications can attach accounting to individual columns without a store-wide accountant. Prepared batches reserve directory, shared-header and required copied-move body growth with each touched column's actual funder before publication. Inserted bodies are caller-admitted beforehand; abort or refusal releases only preparation reservations and retains that caller-admitted body charge. Publication adopts payload charges until the final holder drops; settlement returns unused reserved bytes to their funder. Mixed legacy columns keep their existing insertion protocol.
Detached-copy hydration unwraps only unique, uncharged shared allocations, moves their Vec bodies without cloning and releases redundant headers after they are freed. Shared history and charged or already-resident sources retain their owners; refused hydration releases loader workspace and can retry without losing memberships.
Resident generation materialization selects the pinned snapshot's live entries and optional post-LSN delta before copying bodies. It preserves F32/SQ8/SQ4 representations, intervals and all retained memberships; metadata disagreement refuses before body materialization.