Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }

Implementations§

Source§

impl Engine

Source

pub fn open(path: impl Into<PathBuf>) -> Result<OpenedEngine, EngineOpenError>

Source

pub fn open_with_choice( path: impl Into<PathBuf>, choice: EmbedderChoice, ) -> Result<OpenedEngine, EngineOpenError>

Open an engine with an explicit EmbedderChoice.

Per dev/design/embedder.md §0 + the 0.7.1 EU-5 campaign, this is the canonical entry point for selecting how the workspace’s default embedder is supplied. See EmbedderChoice for the semantics of each variant; in particular Default materializes the pinned BGE embedder via the loader when the default-embedder feature is enabled.

Source

pub fn open_with_migration_event_sink( path: impl Into<PathBuf>, emit_migration_event: impl FnMut(&MigrationStepReport), ) -> Result<OpenedEngine, EngineOpenError>

Source

pub fn path(&self) -> &Path

Source

pub fn write( &self, batch: &[PreparedWrite], ) -> Result<WriteReceipt, EngineError>

Source

pub fn ingest_with_extractor( &self, cmd: &[&str], documents: &[ExtractDocument], ) -> Result<IngestWithExtractorReceipt, EngineError>

G11 (Slice 15) — BYO-LLM ingest: spawn an external extraction harness speaking the fathomdb.extract.v1 NDJSON-over-stdio protocol, send documents for extraction, and write the resulting entities (→ canonical_nodes) and fact-edges (→ canonical_edges with G11 enrichment columns) to the store.

cmd is argv (first element = program, rest = args). Documents are batched per the harness’s max_docs_per_request. Entity logical_id is derived as sha256("<type>:<name>") (lowercase, hex-encoded) for stable cross-re-ingestion identity. Edge logical_id is derived as sha256("<from_lid>:<to_lid>:<relation>"). Both are consistent with G0 supersession: re-ingesting the same document yields the same ids, triggering tombstone-then-insert rather than accumulation.

Returns EngineError::Extractor on protocol errors (bad handshake, subprocess spawn failure, JSON decode error). no_facts warnings from the harness are not errors and do not affect the receipt counts.

Source

pub fn consolidate_with_provider( &self, cmd: &[&str], axes: &[ConsolidateAxis], ) -> Result<ConsolidateReceipt, EngineError>

0.8.12 Slice 15 (OPP-2, ADR-0.8.12) — BYO-LLM CONSOLIDATION / RECENCY.

The SECOND consumer of the one provider_session transport (ADR-0.8.6): consolidation reuses the exact NDJSON-over-stdio transport, hello/ready handshake, supported_tasks negotiation, request_id framing, and bounded-recv timeout — only the protocol string (fathomdb.consolidate.v1) and the task-specific payload differ. There is NO second transport and NO second handshake.

For each (subject, relation) axis, FathomDB assembles a candidate cluster of competing active fact-edges DETERMINISTICALLY (CPU-only, no LLM), sends it to the caller-supplied harness, and applies the returned verdicts. CALLER-SIDE BYO-LLM: the harness is the caller’s subprocess; the library never embeds or calls an LLM and makes NO network egress.

Load-bearing semantic (ADR-0.8.12 §2.1): consolidation records supersession / recency METADATA only — invalidate sets t_invalid, supersede/merge marks the row superseded via the existing G0 tombstone column. Edge BODIES are NEVER rewritten and NO row is ever deleted (the 0.8.3 lesson: blind content-merge HURT accuracy). The original rows survive; the engine stays deterministic.

Returns EngineError::Consolidator on any transport/handshake/protocol fault or a malformed / out-of-cluster verdict.

Source

pub fn search(&self, query: &str) -> Result<SearchResult, EngineError>

Source

pub fn search_view( &self, query: &str, view: &ReadView, ) -> Result<SearchResult, EngineError>

0.8.20 Slice 15b fix-2 (R-20-NV / R-20-RV) — search under an explicit ReadView, the escape hatch matching the one the five read verbs got in Slice 10b. search(query) is exactly search_view(query, &ReadView::default()).

Scope: the VALIDITY axis only. include_out_of_window and valid_as_of are honoured; the EXISTENCE flags (include_superseded, include_inactive) are refused with EngineError::InvalidArgument rather than silently ignored. Relaxing superseded_at IS NULL on a retrieval path would resurrect the stale-body leak the Slice-15 fix-1 review closed, and search hydrates from projection indexes (search_index, vector_default) that are not version-complete — so “include superseded” has no truthful answer here. Refusing says that; ignoring would be the dead surface this fix exists to remove.

Governed surface: PROPOSED / NOT SIGNED (0.8.20 Slice 15b fix-2).

Source

pub fn search_reranked_view( &self, query: &str, filter: Option<SearchFilter>, rerank_depth: usize, use_graph_arm: bool, alpha: f64, pool_n: usize, explain: bool, view: &ReadView, ) -> Result<SearchResult, EngineError>

0.8.20 Slice 15b fix-2 (R-20-NV / R-20-RV) — the FULL-arity view entry point: search_reranked / search_explained under an explicit ReadView. This is what the Python and TypeScript search(..., view=) bindings call, so a caller can combine a content filter, the CE knobs and a validity view in one query — passing view must not silently disable the filter, and passing a filter must not silently disable view.

search_reranked(q, f, d, g, a, p) is exactly search_reranked_view(q, f, d, g, a, p, false, &ReadView::default()).

Validity axis only; existence flags are refused. See search_view.

Governed surface: PROPOSED / NOT SIGNED (0.8.20 Slice 15b fix-2).

Source

pub fn search_filtered( &self, query: &str, filter: Option<SearchFilter>, ) -> Result<SearchResult, EngineError>

G10 — hybrid search with an optional closed SearchFilter. None (or an all-None filter) is the unfiltered path whose phase-1 SQL is byte-identical to 0.7.2. The filter prunes the vector branch in the single phase-1 candidates statement and constrains the text branch by the same metadata. Ranking is the unconditional G9 RRF fusion.

Source

pub fn search_filter( &self, query: &str, filter: &Filter, ) -> Result<SearchResult, EngineError>

0.8.11 Slice 40 (#17) — unified-Filter entry point for the vec0 search backend. Lowers the metadata subset to the indexed pre-KNN WHERE and typed-rejects a FilterTerm::Json term with EngineError::InvalidFilter (D3 no-demotion guarantee). This is the unified surface the 0.8.15 router constraints block reasons over; the shipped Engine::search_filtered(query, Option<SearchFilter>) stays as sugar over the same path.

Source

pub fn search_reranked( &self, query: &str, filter: Option<SearchFilter>, rerank_depth: usize, use_graph_arm: bool, alpha: f64, pool_n: usize, ) -> Result<SearchResult, EngineError>

0.8.1 Slice 10 (R1) / Slice 30 (R3) — search_reranked: hybrid search with optional CE reranking and optional graph-BFS third arm. rerank_depth = 0 is the identity (soft-fallback) path, byte-identical to search_filtered. rerank_depth = N > 0 applies the cross-encoder over the top-N fused hits (when the default-reranker feature is enabled and the model is loaded); without the model, the call falls back to the fused order.

use_graph_arm = false (the default) produces byte-identical results to the pre-Slice-30 two-arm pipeline. use_graph_arm = true seeds a BFS over temporal fact-edges from the top-10 fused hits and fuses the reachable nodes as a third RRF arm.

Governed surface: re-exported from fathomdb facade.

Source

pub fn search_explained( &self, query: &str, filter: Option<SearchFilter>, rerank_depth: usize, use_graph_arm: bool, alpha: f64, pool_n: usize, ) -> Result<SearchResult, EngineError>

0.8.8 EXP-OBS (Slice 5) — search_explained: the opt-in explain=true surface. Identical retrieval to search_reranked (same fused/CE ranking, same results), additionally returning a Explanation sidecar on SearchResult.explanation with per-hit arm provenance + score breakdown + a query-level QueryTrace. The default search/search_filtered/search_reranked paths are unaffected and stay byte-identical (R-OBS-2).

Governed surface: re-exported from fathomdb facade.

Source

pub fn search_text_only(&self, query: &str) -> Result<SearchResult, EngineError>

0.8.18 Slice 5 (#5 vector-equivalence probe, R-VEQ-4) — the explicit text-only / FTS-only search path. It does NOT embed the query and does NOT route through the vector-dependent choke point search_inner_with_stats, so it NEVER raises EngineError::VectorEquivalenceMismatch and stays serviceable when the engine opened in the degraded dense_disabled state (the D2 “keep FTS servable” contract; codex R2 U1-2). Results come from the node-body FTS branch only — no vector recall, no CE rerank, no graph arm. Available regardless of degraded state; when dense is healthy it is simply a text-only view of the same corpus.

Governed surface: re-exported from the fathomdb facade + Py/TS bindings.

Source

pub fn search_text_only_view( &self, query: &str, view: &ReadView, ) -> Result<SearchResult, EngineError>

0.8.20 Slice 15b fix-2 (R-20-NV / R-20-RV) — search_text_only under an explicit ReadView. Same validity-axis-only scope, and the same typed refusal of the existence flags, as search_view.

Governed surface: PROPOSED / NOT SIGNED (0.8.20 Slice 15b fix-2).

Source

pub fn search_projected_text( &self, query: &str, name: &str, filter: Option<SearchFilter>, view: &ReadView, ) -> Result<SearchResult, EngineError>

Search one declared searchable→FTS projection without invoking body search, vector search, score fusion, or a fallback arm. Results carry the ordinary text branch shape and are ordered by property-FTS bm25 ascending then write cursor ascending.

Source

pub fn dense_disabled(&self) -> bool

0.8.18 Slice 5 (R-VEQ-6) — degraded-open observability accessor. true iff the open-time #5 self-check found a vector-equivalence divergence and every vector-dependent arm is refusing. Mirrors OpenReport.dense_disabled; read lock-free.

Source

pub fn dense_disabled_reason(&self) -> Option<String>

0.8.18 Slice 5 (R-VEQ-6) — the human-readable reason for the degraded state (which representation tripped), or None when dense is healthy.

Source

pub fn vector_equivalence_refusal_count(&self) -> u64

0.8.18 Slice 5 (R-VEQ-6) — telemetry counter: number of query-time vector-dependent-arm refusals raised because the engine opened degraded. Observable pre/post-query.

Source

pub fn enable_telemetry(&self, sink_path: &str) -> Result<(), EngineError>

0.8.8 Slice 15 (OPP-9) — enable opt-in telemetry capture to a local JSONL sink_path (append-only). Off by default; once enabled, each search records a query→result event and record_feedback appends agent labels. Local file only — no network/egress. query_id + ts_monotonic_ms are reset deterministically on enable. Idempotent re-enable resets the seq.

Source

pub fn last_telemetry_query_id(&self) -> Option<String>

0.8.8 Slice 15 — the most-recent captured query_id (for record_feedback). None when telemetry is off or no query has been captured yet.

Source

pub fn record_feedback( &self, query_id: &str, relevant_ids: &[u64], irrelevant_ids: &[u64], label_source: &str, ) -> Result<(), EngineError>

0.8.8 Slice 15 — append an agent-supplied relevance-label record for a previously-captured query_id. label_source is the only exogenous string (caller-declared, e.g. "agent:hermes").

ID-SPACE (Cause-A, 0.8.11.2 — honest record). relevant_ids / irrelevant_ids are the interim SearchHit.id == write_cursor (the same space as the captured event’s result_ids), NOT logical_id. The signature is left byte-stable: the gold pipeline maps these write_cursor keys to the cross-session-stable id via the capture event’s parallel result_idsresult_stable_ids arrays (eval/gold_capture.py), so no new feedback parameter — and no binding-signature churn — is required. Errors if telemetry is off.

Source

pub fn _graph_frontier_stats_for_test( &self, query: &str, ) -> Result<GraphFrontierStats, EngineError>

G0 Phase-2 (BLOCK-1) test seam — runs the graph-arm retrieval path and returns the frontier meter (GraphFrontierStats) for query. Mirrors the sanctioned set_vector_stage_only_for_test / _configure_vector_kind_for_test pattern: kept OFF the governed surface (test/eval-only), so the meter never appears on SearchResult. Used by the recall harness to prove the doc-seeded frontier is empty (resolved_seed_rate == 0.0) and, post-C1, the 0→>0 flip.

Source

pub fn read_get( &self, logical_id: &str, view: &ReadView, ) -> Result<Option<NodeRecord>, EngineError>

Slice 30 (G2) — read.get: active-only point lookup by logical_id. Delegates to Engine::read_get_many; returns the single slot. A missing/superseded id is None (a normal absence, not an error). Reads ride the ReaderWorkerPool DEFERRED-tx path (never the writer lock).

Source

pub fn read_get_many( &self, logical_ids: &[String], view: &ReadView, ) -> Result<Vec<Option<NodeRecord>>, EngineError>

Slice 30 (G2) — read.get_many: active-only point lookup over many logical_ids. Returns one slot per requested id in REQUEST ORDER, None where no active row carries that id (partial, never all-or-nothing).

Source

pub fn graph_neighbors( &self, root_logical_id: &str, depth: u32, direction: TraversalDirection, view: &ReadView, ) -> Result<Vec<NodeRecord>, EngineError>

Slice 20 (G5) — read.neighbors: bounded BFS from root_logical_id over canonical_edges. Returns nodes reachable within depth hops (1..=3) in the given direction, excluding the root itself.

Hard cap: 50 results (engine-enforced LIMIT 50). Traversal filter: superseded_at IS NULL AND (t_invalid IS NULL OR t_invalid > now).

Returns Err(EngineError::InvalidArgument) for depth > 3. Returns Ok(vec![]) for an unknown/superseded root. Reads ride the ReaderWorkerPool DEFERRED-tx path.

Source

pub fn search_expand( &self, query: &str, filter: Option<SearchFilter>, depth: u32, ) -> Result<SearchExpandResult, EngineError>

Slice 20 (G6) — search_expand: hybrid search (G1+G9) followed by bounded BFS expansion (G5) of each search hit. Returns the original search hits (with RRF scores) plus nodes reachable from any hit via up to depth hops that are NOT already in the search hit set.

Returns Err(EngineError::InvalidArgument) for depth > 3. A depth = 0 call returns search hits with their logical_ids resolved but no BFS expansion. Reads ride the ReaderWorkerPool DEFERRED-tx path.

Snapshot note: the search phase (search_inner) and the expansion phase (SearchExpand reader request) run in separate DEFERRED reader transactions; a write that lands between them is visible to expansion but not search (or vice-versa). In practice the window is negligible for single-process embedded use. The expansion phase mitigates drift by filtering search_hits to only include hits whose write_cursor is still active in the expansion snapshot (superseded hits are dropped from the result rather than surfaced with stale data).

Source

pub fn read_collection( &self, collection: &str, after_id: Option<i64>, limit: usize, ) -> Result<Vec<OpStoreRow>, EngineError>

Slice 30 (G3) — read.collection: paginated op-store read-back over operational_mutations for collection, ORDER BY id. limit is MANDATORY (clamped to the ~1M cap); after_id is the exclusive cursor. Reads ride the ReaderWorkerPool DEFERRED-tx path.

Source

pub fn read_mutations( &self, collection: &str, after_id: Option<i64>, limit: usize, ) -> Result<Vec<OpStoreRow>, EngineError>

Slice 30 (G3) — read.mutations: the mutation-log-oriented alias surface over the SAME op-store read-back as Engine::read_collection.

Source

pub fn read_list( &self, kind: &str, predicates: &[Predicate], limit: usize, view: &ReadView, ) -> Result<Vec<NodeRecord>, EngineError>

Slice 35 (G4) — read.list: list active canonical_nodes of a given kind, optionally filtered by a closed Predicate set, up to limit rows. Returns Vec<NodeRecord> (active only; superseded_at IS NULL).

Multiple predicates are combined as AND (D-F5). An empty predicate slice returns all active nodes of the given kind up to limit (unfiltered path). Compilation target: json_extract(body, '$.field') <op> ? with bound parameters (injection-safe per D-F4). See dev/adr/ADR-0.8.0-filter-grammar.md.

Path validation happens at Predicate construction time; read_list revalidates as defense-in-depth (enum variants are pub, so direct struct-literal construction could bypass the constructors).

Source

pub fn read_list_filter( &self, kind: &str, filter: &Filter, limit: usize, view: &ReadView, ) -> Result<Vec<NodeRecord>, EngineError>

0.8.11 Slice 40 (#17) — unified-Filter entry point for the canonical_nodes read.list backend. Accepts the full FilterTerm set (D3): Json runs the shipped allowlisted json_extract path; Status/CreatedAfter lower to allowlisted json-paths; Kind/SourceType constant-fold against the partition kind (a guaranteed-empty fold returns an empty Vec without touching SQL). Dispatches to the same Engine::read_list machinery the shipped Predicate surface uses, so every inherited invariant (superseded_at IS NULL, json_valid(body), the canonical_nodes(kind) index, parameterized binds) is preserved.

Source

pub fn crossed_boundary_since( &self, since: i64, view: &ReadView, ) -> Result<Vec<BoundaryCrossing>, EngineError>

0.8.20 Slice 10b (R-20-NV) — the validity-boundary hook: which nodes crossed a [valid_from, valid_until) boundary in the half-open interval (since, as_of]?

since and the resolved upper bound are INTEGER epoch SECONDS. The upper bound is the view’s own instant (view.valid_as_of, defaulting to now), so one instant governs both the boundary interval and the view — and, as everywhere else on this path, it is BOUND, never a datetime('now') literal, so the answer is deterministic for a fixed (since, as_of).

A node appears once, carrying whichever of the two boundaries it crossed; a window that both opened AND closed inside the interval reports both. Rows with an unbounded window on a side cannot cross that side, so a NULL/NULL row (every row predating schema step 22) never appears.

The view’s EXISTENCE flags still apply (so by default only current, active rows are considered), but its validity predicate does NOT: the question is about boundary crossings, not about being valid right now.

When the view relaxes validity entirely (include_out_of_window), the interval is unbounded above.

This is world-time only. There is deliberately no transaction-time (history_as_of) counterpart.

Source

pub fn close(&self) -> Result<(), EngineError>

Source

pub fn drain(&self, timeout_ms: u64) -> Result<(), EngineError>

Block until in-flight writes drain or timeout_ms elapses.

Surface owned by dev/interfaces/rust.md § Engine-attached instrumentation; semantics are owned by dev/design/lifecycle.md.

Source

pub fn counters(&self) -> CounterSnapshot

Snapshot of engine-internal counters.

Field set owned by dev/design/lifecycle.md.

Source

pub fn set_profiling(&self, enabled: bool) -> Result<(), EngineError>

Toggle response-cycle profiling.

Per dev/design/lifecycle.md § Per-statement profiling, profiling is an opt-in surface that is independently toggleable on a running engine without restart. AC-005a locks runtime toggleability.

Source

pub fn set_slow_threshold_ms(&self, value: u64) -> Result<(), EngineError>

Set the threshold above which an operation is reported as slow.

Per dev/design/lifecycle.md § Slow and heartbeat policy, the threshold is runtime-configurable; mutating it changes detection behavior on subsequent statements without restart (AC-007b).

Source

pub fn subscribe(&self, subscriber: Arc<dyn Subscriber>) -> Subscription

Attach a host subscriber to engine events.

Dropping the returned Subscription detaches the subscriber. Payload shape owned by dev/design/lifecycle.md and dev/design/migrations.md.

Source

pub fn embed_text(&self, text: &str) -> Result<Vec<f32>, EngineError>

Embed arbitrary text with the engine’s configured runtime embedder, returning the raw (un-centered) vector.

This is the read-path embed primitive: it mirrors the search query-embedding path — a single, direct Embedder::embed call. The per-embed() watchdog/circuit-breaker guards only the bulk projection/write path (many embeds, fault isolation), not single read-side embeds, so a direct call is consistent with how a query is embedded. Callers get vectors under the engine’s pinned embedder identity (fathomdb-bge-small-en-v1.5 by default) rather than a parallel, possibly-divergent embedder.

Returns EngineError::EmbedderNotConfigured if the engine was opened without an embedder (use_default_embedder = false).

Source

pub fn drain_embedder_events(&self) -> Result<Vec<EmbedderEvent>, EngineError>

0.7.2 PR-2b — NON-test observation seam. Drains and returns every EmbedderEvent queued since the last drain (mean pin, manual mean recompute). Production callers use this to observe the synchronous recompute work; events are queued only AFTER the recompute transaction is durable, so a rolled-back recompute never surfaces. Mirrors the at-open OpenReport.embedder_events channel for the steady-state path.

Source

pub fn write_node_importance( &self, write_cursor: u64, importance: f64, ) -> Result<(), EngineError>

0.8.16 Slice 5 / F9 (R-F9-1) — set the caller-supplied importance ranking scalar on the canonical_nodes row identified by write_cursor (the interim id SearchHit.id carries). Validates importance ∈ [0.0, 1.0], mirroring the existing canonical_edges.confidence write-path check — an out-of-range value is a deterministic EngineError::WriteValidation.

The 3-way sentinel: NOT calling this leaves the column NULL (never assigned = graceful-absent, ranks NEUTRAL); 0.0 is the explicit floor; (0.0, 1.0] is an explicit importance. Importance is a caller-supplied scalar — the engine does NOT compute graph-centrality importance (ADR §4 non-goal). Engine-internal minimal surface for this keystone; SDK (Py/TS) exposure is a Slice-40 concern.

Source

pub fn node_importance( &self, write_cursor: u64, ) -> Result<Option<f64>, EngineError>

0.8.16 Slice 5 / F9 (R-F9-1) — read back the importance scalar for the canonical_nodes row identified by write_cursor. None = SQL NULL = never assigned (graceful-absent). The reciprocal read for Engine::write_node_importance.

Source

pub fn transition( &self, logical_id: &str, to_state: LifecycleState, reason: Option<String>, ) -> Result<(), EngineError>

OPP-12 Phase-1 (0.8.19 Slice 10, R-TR-1/2) — move a governed node between existence states per the engine-enforced legal-transition table (design §2): promote pending→active, reject pending→deleted, soft-delete active→deleted, undelete deleted→active. to_state is a full LifecycleState, but Pending (create-time only) and Purged (purge-only) are never legal transition targets, nor are self-loops or any move from a non-existent/purged row — each returns a typed EngineError::IllegalTransition enumerating the legal targets.

reason semantics (design §3 gap-6): promote/undelete CLEAR reason to NULL (the row is admitted; no standing cause); reject/soft-delete SET reason to the supplied value (NULL allowed but the delete-family expects it). reason is advisory — the engine never interprets it.

Keys on the BARE logical_id (l: space only); a Content(h:) or Passage(p:) id raises EngineError::NotLifecycleAddressable. The state flip mutates the single active (superseded_at IS NULL) row; a deleted row STAYS node-FTS / vector indexed (gap-5) — only the state='active' default filter excludes those shadows, so an undelete needs no re-projection there.

0.8.20 Slice 15d fix-2 [P2] — the row-owned ATTRIBUTE projection (canonical_attributes / property_search_index) is the exception: it has NO read-side lifecycle filter (the property-FTS5 table cannot carry one), so it is maintained AT REST to track the backfill’s set (projected ⟺ active ∧ non-superseded). Promote/undelete PROJECT the declared attributes; soft-delete PURGES them; reject is a no-op.

Source

pub fn configure_projections( &self, specs: &[ProjectionSpec], drop: &[String], ) -> Result<ProjectionDelta, EngineError>

0.8.20 Slice 15d (R-20-PR / C-1) — the projection registry as a DECLARATIVE, IDEMPOTENT apply. The engine is the SOLE projection authority (Q3): it diffs the supplied specs against the durable registry and backfills the difference in ONE transaction. Cheap projections (filterable, searchable→FTS) are built same-transaction; rankable and the searchable→vector sub-target are PERSISTED but deferred (F9 / Slice 20) — declaring them never errors (graceful-absent, Q6a).

0.8.20 Slice 23 (R-20-SV) — SPEC VALIDATION. A spec that carries an fts or vector sub-object WITHOUT ProjectionRole::Searchable is an INVALID SPEC and is refused with EngineError::WriteValidation (HITL 2026-07-24; see [apply_projection_config] for the full rationale). A rejected request is a TOTAL no-op. read_projections is unaffected — it is a pure read — so a LEGACY row in that shape still reports verbatim but can no longer be re-applied.

drop is EXPLICIT (C3, api-surface.md:27): omission of a live projection from specs does NOT drop it; removal requires naming it in drop. An incompatible/destructive change to a live projection that is NOT in drop is refused with EngineError::ProjectionDestructive, the destructive delta surfaced — never silent data loss. Re-applying an unchanged spec diffs to a no-op (ProjectionDelta::unchanged).

Pair with Engine::read_projections to see current state before applying.

Source

pub fn read_projections(&self) -> Result<Vec<ProjectionSpec>, EngineError>

0.8.20 Slice 15d (R-20-PR) — read the current projection registry (C5 introspection: read.projections). Returns every declared ProjectionSpec sorted by name, so a caller can inspect current state (and the destructive delta a change would cause) BEFORE applying. Pure read; never mutates.

0.8.20 Slice 20 (R-20-DR) — this is ALSO the surface that populates the engine-set ProjectionVector::dense_readiness READ METADATA. It is derived here, on the way out (see [derive_dense_readiness]); the durable registry stores no readiness. Only a spec that declares the searchable→vector sub-object carries one — filterable and searchable→FTS are same-transaction and have no readiness axis.

Source

pub fn purge(&self, logical_id: &str) -> Result<(), EngineError>

OPP-12 Phase-1 (0.8.19 Slice 10, R-PG-1/2) — irreversibly hard-erase a governed node. A SEPARATE verb from Engine::transition (NOT on the recovery_denylist). Precondition: DELETED-FIRST — legal only from deleted (else a typed EngineError::IllegalTransition to purged); IDEMPOTENT — purging an already-absent/already-purged id is a no-op success. Keys on the bare logical_id (l: only); h:/p:EngineError::NotLifecycleAddressable.

In ONE transaction, physically erases every ROW-OWNED target for the node (design §3 / gap-3): all canonical_nodes versions; its search_index, search_index_edges, search_index_v2 FTS rows; its vector_default (vec0) + _fathomdb_vector_rows vectors; its _fathomdb_projection_terminal bookkeeping; and — CASCADE-REMOVE, no content-free stubs — every canonical_edges row touching it (from_id/to_id) plus those edges’ projection shadows. The global/kind-level registries _fathomdb_projection_state and _fathomdb_vector_kinds are NOT keyed to a node id and are DELIBERATELY untouched.

Erasure completeness relies on the standing PRAGMA secure_delete=ON (design §3 gap-4) which zeroes every freed page — so no per-purge VACUUM. (Freelist content written on a pre-20 DB before secure_delete was on is a documented residual; there is no forced migration-time VACUUM.)

Source

pub fn erase_source(&self, source_id: &str) -> Result<ExciseReport, EngineError>

0.8.20 Slice 5d (R-20-E4, design §4 item 9b) — the governed SDK erasure verb. Deletes every canonical row attributable to source_id, plus its row-owned projections, and finishes the erasure at rest.

This is NOT operator-gated: erasing content a consumer wrote is an application obligation, not a recovery workflow. Before this slice the only erasure path was [Engine::excise_source], which lives behind the operator feature (i.e. the CLI), so an SDK-only consumer holding a deletion obligation over ANONYMOUS content — content with no logical_id, therefore not reachable by Engine::purge — had no way to discharge it at all. That gap is what R-20-E4 closes.

One engine path. erase_source and excise_source are the SAME operation: both delegate to Engine::erase_source_shared. They are not competing implementations, and no behaviour is duplicated.

Validation differs, deliberately. erase_source admits only ids SourceId::new would admit, so a caller cannot aim the governed verb at the engine’s reserved _-prefixed namespace (_engine:* substrate, or the _legacy:pre-0.8.20 cohort migration step 21 back-filled — a single call against which would erase every pre-0.8.20 anonymous row). excise_source stays permissive precisely BECAUSE it is the recovery seam: R-20-E8 requires an operator to be able to excise _legacy:.

Not a recovery verb. erase_source carries no REQ-054 recovery-denylist name ({recover, restore, repair, fix, rebuild}); it is a lifecycle verb alongside transition/purge. AC-041 is unaffected.

§Errors

EngineError::WriteValidation for an empty, whitespace-only or reserved source_id; EngineError::ErasureIncomplete if the erasure could not be completed at rest (see Engine::complete_erasure_at_rest).

Trait Implementations§

Source§

impl Debug for Engine

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Engine

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl !Freeze for Engine

§

impl !RefUnwindSafe for Engine

§

impl !UnwindSafe for Engine

§

impl Send for Engine

§

impl Sync for Engine

§

impl Unpin for Engine

§

impl UnsafeUnpin for Engine

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> 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.