Skip to main content

Store

Struct Store 

Source
pub struct Store {
    pub root: PathBuf,
    /* private fields */
}
Expand description

Persistent knowledge store for a single mati project.

Wraps two SurrealKV trees:

  • knowledge — user-visible records (gotchas, files, decisions, …)
  • sessions — analytics, hook events, compliance logs

All public methods are async; callers must be in a tokio context.

Fields§

§root: PathBuf

Absolute path to ~/.mati/<slug>/

Implementations§

Source§

impl Store

Source

pub async fn open(repo_root: &Path) -> Result<Self>

Open (or create) both trees for the project rooted at repo_root.

Creates ~/.mati/<slug>/ if it does not exist.

If the search index is corrupt or schema-incompatible, it is wiped and replaced with a fresh empty index. Store::index_needs_rebuild will return true in that case — call Store::rebuild_search_index before issuing any search queries, or use Store::open_and_rebuild which handles this automatically.

Source

pub async fn open_and_rebuild(repo_root: &Path) -> Result<Self>

Open the store and rebuild the search index from SurrealKV if needed.

This is the recommended entry point for the CLI and MCP server. It combines Store::open with an automatic Store::rebuild_search_index call when the index was corrupt or missing (C4). Search queries are safe to issue immediately on the returned store.

Unlike Store::open, this eagerly initializes tantivy so corruption can be detected and recovered from before any queries are issued.

Source

pub fn index_needs_rebuild(&self) -> bool

True when the search index was corrupt or missing on open.

This flag reflects the state detected at open time and is not reset after Store::rebuild_search_index completes. Use it only to decide whether to call rebuild_search_index — not as a post-rebuild status. Store::open_and_rebuild handles this automatically.

Source

pub async fn rebuild_search_index(&self) -> Result<usize>

Rebuild the tantivy search index from scratch by scanning all Record-containing namespaces in SurrealKV (C4).

Must be called on a store whose search index is empty — i.e. immediately after Store::open detected a corrupt/missing index, before any writes. Calling on a non-empty index will produce duplicate entries; use the deduplication in Search::query_keys to tolerate this if it occurs.

Returns the total number of records committed to the index.

Source

pub async fn get(&self, key: &str) -> Result<Option<Record>>

Read a record by key. Returns None if not found.

Source

pub async fn put(&self, key: &str, record: &Record) -> Result<()>

Write a record with the appropriate durability level.

Durability is derived from the key prefix via Durability::for_key.

Source

pub async fn put_batch_kv_only(&self, records: &[(&str, &Record)]) -> Result<()>

Write multiple records to KV only, skipping the tantivy search index.

Use this during bulk init passes where search indexing would block the critical path. Follow with Self::rebuild_search_index to update tantivy from the same in-memory records without a KV round-trip.

Same durability semantics as Self::put_batch: at most 2 fsyncs.

Source

pub fn mark_search_stale(&self)

Mark the search index as stale so the next Self::open_and_rebuild call wipes and rebuilds it from KV.

Written by mati init after a cold init pass to defer the tantivy indexing cost (~400ms on 27k records) to the first MCP server startup. Best-effort: a write failure is silently discarded — the worst outcome is that the search index contains stale data until the next full rebuild.

Source

pub async fn put_batch(&self, records: &[(&str, &Record)]) -> Result<()>

Write multiple records in a single transaction per durability class.

Records are grouped by their key prefix: all Immediate keys share one transaction on knowledge (1 fsync), all Eventual keys share one on sessions (1 fsync). The whole batch costs at most 2 fsyncs regardless of how many records it contains — critical for Layer 0 bulk inserts.

Empty slice is a no-op. Mixed-durability batches are handled correctly.

Source

pub async fn delete(&self, key: &str) -> Result<()>

Delete a record by key. No-op if the key does not exist.

Source

pub async fn scan_prefix(&self, prefix: &str) -> Result<Vec<Record>>

Return all records whose key starts with prefix.

Prefix must use one of the known key namespaces so the correct tree is selected. Unknown prefixes are scanned from knowledge.

Return order is not guaranteed. Callers that need a stable order must sort.

Source

pub async fn scan_prefix_each<F>(&self, prefix: &str, callback: F) -> Result<()>
where F: FnMut(Record),

Scan records whose key starts with prefix, invoking callback for each.

Same tree routing and prefix semantics as Self::scan_prefix, but records are deserialized and passed to callback one at a time rather than collected into a Vec. Callers can begin processing (e.g. printing to stdout) before the full scan completes, giving time-to-first-row latency proportional to a single deserialization rather than the full scan.

Return order is lexicographic (underlying KV order). Callers that need a different order must collect and sort after the fact.

Source

pub async fn search(&self, text: &str, limit: usize) -> Result<Vec<Record>>

Full-text BM25 search over all indexed records.

Calls tantivy for the top limit matching keys, then fetches each full record from SurrealKV. Keys that tantivy returns but are not found in the store (e.g. deleted since last commit) are silently skipped.

Returns results ordered by descending BM25 relevance score. Returns an empty Vec when text is blank or limit is 0.

Source

pub async fn search_scored( &self, text: &str, limit: usize, ) -> Result<Vec<(f32, Record)>>

Full-text BM25 search returning (score, Record) pairs.

Same semantics as Self::search but preserves the raw BM25 relevance score from tantivy. Used by mem_query text mode to include relevance in the agent-facing response.

Source

pub async fn get_raw_bytes(&self, key: &str) -> Result<Option<Vec<u8>>>

Read raw bytes by key. Returns None if the key does not exist.

Counterpart to Self::put_raw. Used for structural metadata, enforcement events, and other non-Record values stored as raw bytes.

Source

pub async fn put_raw(&self, key: &str, value: &[u8]) -> Result<()>

Write raw bytes under key with automatically routed durability.

Same durability routing as Self::put — callers do not need to know which tree a key belongs to. Use this for structural metadata (graph edges, etc.) where the value is not a Record and does not need to be deserialised on reads.

Source

pub async fn put_batch_raw(&self, records: &[(&str, &[u8])]) -> Result<()>

Write multiple raw-byte values in a single transaction per durability class.

Same batch semantics as Self::put_batch (at most 2 fsyncs for the whole batch). Use for bulk structural writes like graph edge inserts.

Source

pub async fn transact_knowledge( &self, ops: &[KnowledgeWriteOp<'_>], ) -> Result<()>

Atomically commit multiple writes to the knowledge tree in a single transaction.

Supports mixed Record + raw byte writes. All keys MUST route to the knowledge tree (Durability::Immediate). Returns an error if any key routes to sessions.

Handles crash-fence + tantivy sync + write-seq after commit. Use this for mutation + audit atomic commit on knowledge-side commands.

Source

pub async fn transact_sessions_raw( &self, entries: &[(&str, &[u8])], ) -> Result<()>

Atomically commit multiple raw byte writes to the sessions tree in a single transaction.

All keys MUST route to the sessions tree (Durability::Eventual). Returns an error if any key routes to knowledge.

Use this for mutation + audit atomic commit on session-side commands.

Source

pub async fn scan_keys(&self, prefix: &str) -> Result<Vec<String>>

Return all keys whose prefix matches, without deserialising values.

Cheaper than Self::scan_prefix when only the key is needed (e.g. graph edge loading, existence checks). Uses the SurrealKV iterator key().user_key() path so value bytes are never read from disk.

Source

pub fn history(&self, key: &str, limit: usize) -> Result<Vec<HistoryEntry>>

Return version history for a single key, newest first.

Includes tombstones (deletions). Uses the tight upper bound key + \0 so adjacent keys never spill into the result set.

limit caps the number of entries returned; 0 means unlimited.

Source

pub fn history_since( &self, key: &str, since_ts: u64, limit: usize, ) -> Result<Vec<HistoryEntry>>

Return version history for a single key since since_ts (seconds), newest first.

Timestamps are converted to nanoseconds for the SurrealKV range filter.

Source

pub async fn records_since( &self, since_ts: u64, limit: usize, ) -> Result<Vec<Record>>

Return all records updated since since_ts (seconds), newest first.

Scans every knowledge namespace (including dep:) and returns records whose updated_at >= since_ts. Results are sorted by updated_at descending with secondary sort by key for deterministic ordering.

Source

pub async fn close(self) -> Result<()>

Flush and close both trees, releasing the LOCK files.

Must be called before dropping Store if another process (or test) will reopen the same database directory. SurrealKV holds an exclusive lock for the lifetime of a Tree; reopening without closing first fails with “already locked by another process”.

Source

pub async fn flush_for_shutdown(&self)

Best-effort durability flush for shutdown paths.

Calls SurrealKV’s flush_wal(sync=true) on both trees so every previously-committed transaction reaches disk. Non-consuming and &self — works through a shared Arc<RwLock<Graph>> read lock on the daemon shutdown path where ownership cannot be reclaimed.

Necessary because SurrealKV’s Tree::Drop only fire-and-forget-spawns core.close() onto the current tokio runtime; if the runtime is shutting down (signal handler, main return) that spawned task may not run before the process exits, losing buffered “Eventual” writes.

Errors are logged via tracing::warn! and not propagated — shutdown paths must be infallible. Search index pending writes are committed per Search::add_record/add_records call, so no separate flush is needed here.

Source

pub async fn ping(&self) -> Result<u64>

Ping the store. Writes a sentinel key and reads it back; returns round-trip latency in microseconds.

Used by mati ping and by hook fast-path availability checks.

Source

pub fn read_write_seq(&self) -> u64

Read the current knowledge write-sequence counter.

Returns 0 if the file does not exist or cannot be parsed — callers treat 0 as “no valid cached snapshot” and recompute.

Source

pub fn sessions_tree(&self) -> &Tree

Direct access to the sessions tree for audit reads in tests.

Production code should use the key-routing methods (get, put_raw, scan_keys) rather than accessing trees directly.

Trait Implementations§

Source§

impl RepairReader for Store

Source§

async fn get(&self, key: &str) -> Result<Option<Record>>

Source§

async fn scan_prefix(&self, prefix: &str) -> Result<Vec<Record>>

Source§

async fn scan_keys(&self, prefix: &str) -> Result<Vec<String>>

Auto Trait Implementations§

§

impl !Freeze for Store

§

impl !RefUnwindSafe for Store

§

impl !UnwindSafe for Store

§

impl Send for Store

§

impl Sync for Store

§

impl Unpin for Store

§

impl UnsafeUnpin for Store

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Send + Sync>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Fruit for T
where T: Send + Downcast,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more