pub struct Engine { /* private fields */ }Expand description
Unified engine. Holds a list of mounted backends and routes mem-named operations to the right one.
Send so the engine can sit behind a Mutex (today’s pattern
in the MCP server). The trait object’s Send + Sync bound on
MemBackend keeps the inner backends thread-safe; the engine
itself is single-threaded by design (the lazy memos are
OnceCell, which is !Sync).
Debug is hand-written to avoid requiring Debug on the
dyn MemBackend trait object — backend impls are free to
stay non-Debug.
§Load-on-init
Engine::from_mounts walks each backend at construction time
(list_entities + read_entity + parse) and populates a single
shared Store with entities and edges from every mount. Each
mount’s schema resolves from its own pin (the backend config’s
schema, or the mount-record assertion as fallback) through the
SchemaResolver, so schemas holds genuinely heterogeneous
schemas in a multi-schema workspace. Per-file errors don’t fail
construction; they collect into Engine::load_errors for the
operator to inspect.
Implementations§
Source§impl Engine
impl Engine
Sourcepub fn apply_external_commit(
&mut self,
envelope: &CommitEnvelope,
) -> Result<(), EngineError>
pub fn apply_external_commit( &mut self, envelope: &CommitEnvelope, ) -> Result<(), EngineError>
Replay an externally-produced commit envelope into the in-memory store.
Refuses with EngineError::UnknownMem when the envelope
names a mem the engine has no mount for. Parse failures on
any change variant surface as EngineError::Parse and abort
the apply before any store mutation lands — the post-state is
either coherent at the envelope SHA or unchanged. (The store
mutation loop below uses a staged scratch list precisely to
preserve this all-or-nothing property; do not refactor it
into per-change in-place mutations without restoring an
equivalent guarantee.)
Empty changes is a valid envelope: the head cursor advances
and a MemChangedEvent fires, but no store mutation
happens. Lets replay drivers signal “we saw a commit, here is
its SHA, no entity-level changes” — useful for empty commits
(e.g. tag-only or merge commits without tree changes).
Source§impl Engine
impl Engine
Sourcepub fn from_archive_bytes(bytes: Vec<u8>) -> Result<Self, FromArchiveBytesError>
pub fn from_archive_bytes(bytes: Vec<u8>) -> Result<Self, FromArchiveBytesError>
Hydrate an engine from sealed archive bytes (.mem).
Validates the bytes through the archive ingress validator
(extract_entries), reads the embedded .memstead/config.json for
mem name + schema pin, loads any embedded schema package
(.memstead/schema/) into the engine’s schema catalogue, and
constructs a single-mount read-only engine backed by the bytes.
No temp file, no on-disk artifact — the bytes are the storage.
The resulting engine refuses mutations (memstead_create,
memstead_update, memstead_delete, memstead_relate, memstead_rename) via
the existing read-only-mount / sealed-backend envelope. Read
operations work for the embedded mem.
Sourcepub fn from_archive_bytes_with_limits(
bytes: Vec<u8>,
limits: &ValidatorLimits,
) -> Result<Self, FromArchiveBytesError>
pub fn from_archive_bytes_with_limits( bytes: Vec<u8>, limits: &ValidatorLimits, ) -> Result<Self, FromArchiveBytesError>
Variant of Self::from_archive_bytes with caller-supplied
limits. Bridge / registry deployments tune the caps; the
default ladder (ValidatorLimits::DEFAULT) is what
from_archive_bytes picks.
Sourcepub fn export_mem_to_bytes(
&self,
mem_name: &str,
) -> Result<Vec<u8>, EngineError>
pub fn export_mem_to_bytes( &self, mem_name: &str, ) -> Result<Vec<u8>, EngineError>
Export the named mem’s current state as .mem archive bytes.
Symmetric to Self::from_archive_bytes: a mem name in, a
self-contained byte buffer out. The bytes validate against
extract_entries standalone — any consumer of sealed archives
accepts them. Feeding the bytes back into
Engine::from_archive_bytes yields an engine that returns
identical reads against the exported mem.
Returns EngineError::UnknownMem when the name resolves to
no mount; EngineError::Backend wrapping
crate::backend::BackendError::Sealed when the mem is
archive-mounted (already-an-archive, no meaningful re-export);
EngineError::InvalidInput when the mem has no loaded
MemConfig; EngineError::MemConfigIncomplete when the
loaded config is missing version. The git-branch byte-export
path lifts in a follow-up; today it surfaces as
EngineError::Backend wrapping the unmounted-hook message.
Source§impl Engine
impl Engine
Sourcepub fn from_mounts(
mounts: Vec<(Mount, Box<dyn MemBackend>)>,
) -> Result<Self, EngineError>
pub fn from_mounts( mounts: Vec<(Mount, Box<dyn MemBackend>)>, ) -> Result<Self, EngineError>
Build an engine from (mount, backend) pairs. The backend
is the implementor that will serve reads / writes for that
mount’s mem.
Returns EngineError::DuplicateMem when two mounts name
the same mem; that’s a configuration error the caller must
fix before the engine can route deterministically. An empty
mount list is allowed (returns an engine that errors
UnknownMem on every read) — useful for tests; production
callers will reject empty inputs at the persistence-adapter
layer.
Sourcepub fn from_mounts_with_schemas_dir(
mounts: Vec<(Mount, Box<dyn MemBackend>)>,
schemas_dir: Option<&Path>,
) -> Result<Self, EngineError>
pub fn from_mounts_with_schemas_dir( mounts: Vec<(Mount, Box<dyn MemBackend>)>, schemas_dir: Option<&Path>, ) -> Result<Self, EngineError>
Construct an engine from mounts plus an optional workspace
schemas directory. Loads every subdirectory of schemas_dir
as a workspace-authored schema and combines with the builtin
catalogue for per-mem schema-pin resolution. Workspace
schemas take precedence on (name, version) collision —
matches full’s behaviour.
schemas_dir = None is equivalent to Self::from_mounts.
Used by engine_from_workspace_root to thread the
[schemas_dir] workspace-toml entry into schema resolution.
Sourcepub fn from_mounts_with_schemas_dir_and_extra(
mounts: Vec<(Mount, Box<dyn MemBackend>)>,
schemas_dir: Option<&Path>,
extra: Vec<Arc<Schema>>,
) -> Result<Self, EngineError>
pub fn from_mounts_with_schemas_dir_and_extra( mounts: Vec<(Mount, Box<dyn MemBackend>)>, schemas_dir: Option<&Path>, extra: Vec<Arc<Schema>>, ) -> Result<Self, EngineError>
Like Self::from_mounts_with_schemas_dir but layers additional,
pre-loaded local-storage schemas (e.g. those a git-branch backend
reads from its __MEMSTEAD:schemas/ ref via SchemaSource) on
top of the folder schemas_dir set. Both are local-storage
schemas — they override built-ins on (name, version) collision.
The git-branch boot path uses this to make ref-installed schemas
resolvable, which from_mounts_with_schemas_dir (folder only)
does not.
Sourcepub fn from_workspace_root(workspace_root: &Path) -> Result<Self, BootError>
pub fn from_workspace_root(workspace_root: &Path) -> Result<Self, BootError>
Boot an engine from a workspace root using only lean-flavour backends (folder + archive). The MCP filesystem server, the CLI’s lean dispatcher, and the macOS UniFFI consumer all reach the new engine through this entry point — replacing per-flavour init code with one call.
Loads the workspace through crate::FileWorkspaceStore,
instantiates each mount’s backend via
crate::instantiate_lean_backend, and constructs the
engine via Engine::from_mounts.
Errors:
Layout::Empty→BootError::NotInitialised- any mount declaring
crate::workspace::MountStorage::GitBranch→BootError::Instantiatewrappingcrate::InstantiateError::GitBranchRequiresMemRepoFeature - underlying store / engine failures lift through the
#[from]conversions
Source§impl Engine
impl Engine
Sourcepub fn reload_if_stale(&mut self, mem: Option<&str>) -> Vec<WarningHint>
pub fn reload_if_stale(&mut self, mem: Option<&str>) -> Vec<WarningHint>
Reload-before-operation: before any read or write executes,
check the mem ref; if it advanced past the engine’s cached
last_known_head, reload the affected mem(s) and return one
[WarningHint::MemReloaded] per reload so the caller can
surface the drift to the agent (the response itself already
carries fresh content — the warning explains why state
shifted).
The ref check runs on every call — there is no throttle
window. A per-operation current_head() read is microseconds,
effectively free at LLM latencies, and a throttle that let an
operation execute against an already-moved ref would reintroduce
the exact silent-staleness this guards against. This is the
correctness floor: no operation acts on a projection that is
behind git truth.
mem = Some(name) scopes the probe to one mount; None
scans every mount. Read handlers that target a known mem
(memstead_entity derives the mem from the id;
memstead_changes_since takes it as a param) and every mutation
(which knows its target mem) pass a name; tools that scan
multi-mem (memstead_search without a mem filter,
memstead_overview, memstead_health) pass None.
Behaviour matrix per mount:
- cached
Some(old)+ on-diskSome(new),old != new→ reload the mem, emitMemReloaded, refresh the cached head tonew. - cached
None+ on-diskSome(new)→ silently capture the first observed head as the baseline (no warning — there’s no prior in-memory snapshot to be stale against). - cached / on-disk match, on-disk
None(folder, archive, refdb hiccup), orcurrent_headerrors → no-op.
Reload errors are warn-logged and the affected mem is skipped — the caller’s response is still served from the (now stale) in-memory snapshot rather than failing the entire request. The next operation retries.
Cache invalidation rides on reload_one_mem — community
and search-index memos drop when any mem reloads.
Sourcepub fn take_mem_changed_notices(&mut self) -> Vec<MemChangedNotice>
pub fn take_mem_changed_notices(&mut self) -> Vec<MemChangedNotice>
Drain the reload-before-operation notices accumulated since the
last drain. The response layer calls this after an operation
completes to attach the structured mem_changed notice. Every
handler that can trigger a reload (directly via
Self::reload_if_stale or indirectly through a mutation) must
drain, or an undrained notice leaks into the next operation’s
response.
Sourcepub fn mem_changed_notice(
&self,
mem: &str,
from_head: &str,
to_head: &str,
) -> MemChangedNotice
pub fn mem_changed_notice( &self, mem: &str, from_head: &str, to_head: &str, ) -> MemChangedNotice
Build a crate::ops::MemChangedNotice describing the
per-entity delta a reload applied to mem (from from_head
to to_head). Derived from Self::changes_since so it
carries rename detection on git-branch mounts; on any backend
error (e.g. an unresolvable cursor) it falls back to an empty
delta — the heads alone still tell the agent the mem moved.
Callers pair this with Self::reload_if_stale: a returned
crate::ops::WarningHint::MemReloaded carries the
old_head / new_head to pass here. The delta matches the
transition the reload applied (changes_since walks the same
from_head → current range).
Sourcepub fn changes_since(
&self,
mem: &str,
since: &str,
rename_similarity: Option<f32>,
) -> Result<ChangesReport, EngineError>
pub fn changes_since( &self, mem: &str, since: &str, rename_similarity: Option<f32>, ) -> Result<ChangesReport, EngineError>
Per-entity events for mem between since and the backend’s
current state.
- Resolves the mount (returns
EngineError::UnknownMemon unknown mem). - Validates
rename_similarityagainst[RENAME_SIMILARITY_MIN, RENAME_SIMILARITY_MAX]. Out-of-range values refuse withEngineError::InvalidInputcarryingdetails.allowed_rangeanddetails.requested.Nonefalls back tocrate::ops::RENAME_SIMILARITY_DEFAULT. - Dispatches on the mount’s
MountStorage:- Folder mounts synthesize from the JSONL changelog via
crate::ops::folder_changes_since. - Git-branch mounts call the registered
[
GitBranchOps::changes_since] hook (real tree-diff with rename detection); missing hook = full flavour not loaded and the report comes back empty. - Archive mounts return an empty report.
- Folder mounts synthesize from the JSONL changelog via
- Enriches each envelope’s
title/entity_typefrom the in-memory store (best-effort —Removedenvelopes always leave bothNone; missing-from-store entities also leave themNone). - Returns
crate::ops::ChangesReportwithmem,since(echoed),head(backend-resolved current cursor), enrichedchanges, and any clamping warnings.
Sourcepub fn fetch(
&self,
mem: &str,
remote: &str,
refspecs: &[String],
) -> Result<FetchOutcome, EngineError>
pub fn fetch( &self, mem: &str, remote: &str, refspecs: &[String], ) -> Result<FetchOutcome, EngineError>
Fetch updates from remote into the workspace’s mem-repo.
Advances remote-tracking refs only; the local branch pointer
is not moved.
refspecs is forwarded verbatim to git fetch. An empty list
uses the remote’s configured defaults.
Refusal codes: UNKNOWN_MEM, UNKNOWN_REMOTE,
INVALID_INPUT (folder / archive mounts).
V1 atomicity: schema-validation quarantine for fetched commits is not yet wired. The remote-tracking refs advance unconditionally on a successful fetch; downstream schema validation runs on read via the engine’s existing reload pipeline.
Sourcepub fn pull(
&mut self,
mem: &str,
remote: &str,
) -> Result<PullOutcome, EngineError>
pub fn pull( &mut self, mem: &str, remote: &str, ) -> Result<PullOutcome, EngineError>
Pull updates from remote into the named mem’s branch.
Fetches into the remote-tracking ref, runs a pre-merge schema
validation pass against the prospective state, then
fast-forwards the local branch. Refuses with
LOCAL_DIVERGENCE for diverged local branches and with
SCHEMA_VIOLATION_IN_FETCH when the prospective state fails
schema validation — in both refusal cases the local branch
pointer is untouched (the underlying fetch has updated
refs/remotes/* but the engine has not promoted the new
state).
Sourcepub fn push(
&self,
mem: &str,
remote: &str,
force: bool,
) -> Result<PushOutcome, EngineError>
pub fn push( &self, mem: &str, remote: &str, force: bool, ) -> Result<PushOutcome, EngineError>
Push the named mem’s branch to remote. Runs a pre-push
schema validation pass against the local branch tree; refuses
with LOCAL_INVALID_STATE when the local state fails schema
validation (the remote is not contacted in that case). Refuses
with NON_FAST_FORWARD when the push is not a fast-forward
and force: false; with force: true runs a
--force-with-lease push instead.
Sourcepub fn remote_add(
&self,
name: &str,
url: &str,
) -> Result<RemoteAddOutcome, EngineError>
pub fn remote_add( &self, name: &str, url: &str, ) -> Result<RemoteAddOutcome, EngineError>
Configure (or re-point) a named remote on the workspace’s
mem-repo, so fetch / pull / push have somewhere to go.
Upsert semantics — safe to re-run with a new URL. The mem-repo
is shared by every git-branch mount, so the op is
workspace-level: any git-branch mount locates it; refuses
INVALID_INPUT when the workspace has none.
Sourcepub fn branch_reset(
&mut self,
mem: &str,
target_sha: &str,
) -> Result<BranchResetOutcome, EngineError>
pub fn branch_reset( &mut self, mem: &str, target_sha: &str, ) -> Result<BranchResetOutcome, EngineError>
Reset a mem’s branch pointer to target_sha. The only
engine surface that moves a branch pointer over existing
commits — every other mutation appends. Refuses if any commit
that would be discarded by the reset is already reachable from
a refs/remotes/* ref (the engine’s definition of “pushed”).
target_sha accepts anything gix::rev_parse_single admits:
a SHA, an abbreviated SHA, a branch name, a tag. The branch
itself (refs/heads/<mem>) must exist.
Refusal codes:
EngineError::UnknownMem(UNKNOWN_MEM)EngineError::UnknownRef(UNKNOWN_REF) — branch or target ref does not resolve.EngineError::PushedCommitsProtected(PUSHED_COMMITS_PROTECTED) — at least one discarded commit is pushed. The error carries the offending SHAs verbatim.EngineError::InvalidInput(INVALID_INPUT) — mem is folder / archive-backed (history rewriting only makes sense for git-branch mounts).
Emits a crate::engine::events::MemChangedEvent on
success when the SHA actually changed; the reset’s effect is
observable through the same change-event surface every commit
flows through. Engine’s cached last_known_head for the
affected mount is rewound to the new SHA so the next drift
probe doesn’t flag the reset as a sibling-writer surprise.
Sourcepub fn diff(
&self,
mem: &str,
ref_a: &str,
ref_b: &str,
config: Option<DiffConfig>,
) -> Result<Diff, EngineError>
pub fn diff( &self, mem: &str, ref_a: &str, ref_b: &str, config: Option<DiffConfig>, ) -> Result<Diff, EngineError>
Two-ref structural diff. Produces a per-entity crate::ops::Diff
comparing the trees at ref_a and ref_b for the named
mem’s storage. Folder and archive backends carry no git
refs and refuse via EngineError::InvalidInput; the
git-branch backend routes through [GitBranchOps::diff] when
the full flavour is loaded.
mem selects the storage context (the gitdir, for
git-branch mounts). ref_a / ref_b are arbitrary refs the
underlying git layer accepts — branch names, commit SHAs, tag
names — so cross-mem diffs work via fully-qualified refs
(refs/heads/<other-mem>) without a separate API.
Refusal codes:
EngineError::UnknownMem(UNKNOWN_MEM) — no mount formem.EngineError::UnknownRef(UNKNOWN_REF) — either ref does not resolve. Surfaces verbatim from the git layer’srev_parserefusal.EngineError::RenameSimilarityOutOfRange(INVALID_INPUT) —config.rename_similarityoutside[0.1, 1.0].EngineError::InvalidInput(INVALID_INPUT) — mem is folder or archive-backed (no refs to diff).
Source§impl Engine
impl Engine
Sourcepub fn subscribe_mem_changes(
&self,
mem: &str,
callback: EventCallback,
) -> Result<SubscriptionHandle, EngineError>
pub fn subscribe_mem_changes( &self, mem: &str, callback: EventCallback, ) -> Result<SubscriptionHandle, EngineError>
Subscribe to commit events on mem. The returned
SubscriptionHandle keeps the registration alive; dropping
it (or calling unsubscribe()) removes the callback.
callback runs on the engine’s mutation thread synchronously
— by design, per the Core’s runtime-agnostic contract.
Consumers that cannot block the writer must decouple inside the
callback (channel send, dedicated thread, async runtime
queue). The opt-in tokio feature lifts this into a
broadcast::Receiver for tokio-resident consumers; the
file-watcher feature provides a cross-process variant for
readers without a writer engine.
Read-only mounts (archive or ReadOnly capability) accept the
subscription but never emit — no mutations land in those mems
through this engine. Unknown mems refuse with
crate::EngineError::UnknownMem; the typed code is
UNKNOWN_MEM.
Source§impl Engine
impl Engine
Sourcepub fn subscribe_mem_changes_broadcast(
&self,
mem: &str,
) -> Result<(SubscriptionHandle, Receiver<MemChangedEvent>), EngineError>
pub fn subscribe_mem_changes_broadcast( &self, mem: &str, ) -> Result<(SubscriptionHandle, Receiver<MemChangedEvent>), EngineError>
Tokio-broadcast convenience over the callback subscribe API.
Returns a (SubscriptionHandle, broadcast::Receiver) pair: the
handle keeps the registration alive (drop to unsubscribe); the
receiver yields MemChangedEvents on every commit.
Backpressure follows tokio::sync::broadcast semantics: a
subscriber that falls behind by more than the channel capacity
(DEFAULT_BROADCAST_CAPACITY) sees RecvError::Lagged(n) on
its next recv() and the channel resumes from there. Slow
subscribers do not block the writer — that is the whole point
of the tokio convenience over the raw callback path, where a
slow callback blocks the mutation thread by design.
Use Self::subscribe_mem_changes_broadcast_with_capacity
when the default capacity is too small (high-burst write loops)
or too large (memory-constrained deployments).
Sourcepub fn subscribe_mem_changes_broadcast_with_capacity(
&self,
mem: &str,
capacity: usize,
) -> Result<(SubscriptionHandle, Receiver<MemChangedEvent>), EngineError>
pub fn subscribe_mem_changes_broadcast_with_capacity( &self, mem: &str, capacity: usize, ) -> Result<(SubscriptionHandle, Receiver<MemChangedEvent>), EngineError>
Caller-tunable variant of
Self::subscribe_mem_changes_broadcast. capacity is the
tokio-broadcast channel buffer size; values below 1 panic per
the tokio contract.
Source§impl Engine
impl Engine
Sourcepub fn set_settings(&mut self, settings: WorkspaceSettings)
pub fn set_settings(&mut self, settings: WorkspaceSettings)
Replace the workspace-level settings. Called by
Self::from_workspace_root (and the full counterpart) after
reading .memstead/workspace.toml. Tests / direct callers leave
the default empty value in place. Cheap clone — settings
carry only data shapes (raw rule lists, link policy map),
no compiled matchers. Invalidates the lazy
create_rule_set_memo so the next synthesis call rebuilds
from the new policy.
Sourcepub fn set_backend_factory(&mut self, factory: BackendFactory)
pub fn set_backend_factory(&mut self, factory: BackendFactory)
Replace the backend factory. Full consumers call this once at boot
(engine_from_workspace_root) to install
memstead_git_branch::storage::instantiate_full_backend so the engine
can materialise git-branch backends on top of folder + archive.
Lean consumers leave the default in place.
Sourcepub fn set_git_branch_ops(&mut self, ops: GitBranchOps)
pub fn set_git_branch_ops(&mut self, ops: GitBranchOps)
Install the git-branch ops bundle. Full boot
(memstead_git_branch::engine_from_workspace_root) calls this once
at construction. Lean consumers leave it unset and the
git-branch dispatch branches collapse to typed errors / empty
reports — lean has no git-branch mounts.
Sourcepub fn install_schema(
&self,
name: &str,
version: &str,
files: &[(String, Vec<u8>)],
) -> Result<String, EngineError>
pub fn install_schema( &self, name: &str, version: &str, files: &[(String, Vec<u8>)], ) -> Result<String, EngineError>
Install a schema package onto the workspace’s git-branch backend —
the unified __MEMSTEAD:schemas/<name>@<version>/ ref. files
are (relative-path, bytes) pairs (schema.yaml,
types/<t>.yaml, optional mem-template.json). Returns the
resulting commit sha; idempotent at the storage layer (an
identical re-install produces no new commit).
Folder workspaces install schemas by writing under
<workspace>/.memstead/schemas/ directly; this is the git-branch
path, where the engine owns the mem-repo and the write must
route through it. Errors when no git-branch ops are wired (lean
flavour) or no git-branch mount exists to resolve the shared
mem-repo gitdir from. The caller reloads (or restarts) to pick
the new schema into the resolution catalogue.
Sourcepub fn unregister_writable_mem(
&mut self,
mem_name: &str,
) -> Result<Option<Box<dyn MemBackend>>, EngineError>
pub fn unregister_writable_mem( &mut self, mem_name: &str, ) -> Result<Option<Box<dyn MemBackend>>, EngineError>
Unregister a writable mem at runtime. Engine-level
primitive that memstead_mem_delete builds on.
Removes the named mount from Self::mounts, drops the
mem’s entities from the store, refreshes the
[MemRouterSnapshot] via Arc::make_mut (COW swap so
readers holding a pre-swap snapshot see the pre-state for
their lifetime), and invalidates the community + search
memos. Does NOT touch the backend’s on-disk state — the
caller (delete_mem orchestrator) decides whether to
remove the directory / gitdir after this returns.
Returns Ok(Some(backend)) when the mem was present and
unregistered — the caller can drive any backend-specific
follow-up cleanup (backend.delete_artifacts() for the
mem-repo branch + __MEMSTEAD config when delete_files=true).
Returns Ok(None) when no mount named the mem (idempotent —
repeated calls are safe).
Sourcepub fn register_writable_mem(
&mut self,
mount: Mount,
backend: Box<dyn MemBackend>,
origin: MemOrigin,
) -> Result<(), EngineError>
pub fn register_writable_mem( &mut self, mount: Mount, backend: Box<dyn MemBackend>, origin: MemOrigin, ) -> Result<(), EngineError>
Register a writable mem at runtime. Engine-level primitive
that memstead_mem_create builds on.
Steps:
- Name collision probe against the current
mem_routersnapshot. Writable AND read-only entries collide; the error surfaces the colliding source so the orchestrator can render a recovery hint. - Schema resolution via the built-in catalogue (mirrors
Self::from_mounts; workspace-authored schema resolution lifts later). - Per-mem config load (folder backends only; git-branch /
archive return None — same contract as
Self::from_mounts). - Entity load via the backend, parse, push into the engine’s
store with a
LoadCollectorso drift warnings forward toself.load_warnings. - Insert schema into
Self::schemas. - Push the [
MountedBackend] intoSelf::mounts. - COW snapshot swap on
Self::mem_routerviaArc::make_mut+add_writable(name, dir, origin, mem_path). Folder mounts surface their on-disk path; other backends register withdir: None(matches full’s contract).mem_pathcarries the create-time organisationalpathcomponent (mirrorsMemCreateParams.path) — the delete-side lifecycle composer reads it back to rebuild the<mem_path>/<name>candidate the create-side composer matched against. Caller threadsNonefor flat-layout registrations andSome(p)for hierarchical ones. - Invalidate community + search memos.
Returns Err(EngineError::MemNameCollision) when the name
is already registered. Other failures (schema-not-found,
backend read errors) propagate as their typed variants. On
failure no engine mutation happens: every potentially-
mutating step runs only after the collision probe succeeds,
and intermediate failures propagate before the mount /
router are touched.
Sourcepub fn set_workspace_root(&mut self, root: PathBuf)
pub fn set_workspace_root(&mut self, root: PathBuf)
Override the workspace root after construction. The full
boot helper memstead_git_branch::engine_from_workspace_root
calls this so the engine knows the path even when the boot
route runs through the full adapter rather than
Self::from_workspace_root.
Sourcepub fn persist_state(&self) -> Result<(), EngineError>
pub fn persist_state(&self) -> Result<(), EngineError>
Persist the engine’s current mount list to the workspace
store so a freshly-booted sibling process observes the same
mem membership. Called by
[crate::mem_management::create_mem] /
[crate::mem_management::delete_mem] after the in-memory
router mutation lands — without this, the per-mem content
(branch + __MEMSTEAD config blob, or folder + .memstead/config.json)
is already on disk, but the next process boot reads an empty
.memstead/state/mounts.json and the engine starts with zero
writable mems.
No-op when workspace_root is unset (tests / ad-hoc
consumers that build the engine directly from a mount list).
Production boot paths (Engine::from_workspace_root and the
full counterpart) always set the root, so the engine-side
fix covers every caller — including the future UniFFI binding
— by construction.
Hardcoded against crate::FileWorkspaceStore because that
is the only V1 adapter; a future SQLite or remote adapter
would install through a setter mirroring
Self::set_backend_factory.
Sourcepub fn set_mem_schema(
&mut self,
mem: &str,
target: &SchemaRef,
) -> Result<SetSchemaOutcome, EngineError>
pub fn set_mem_schema( &mut self, mem: &str, target: &SchemaRef, ) -> Result<SetSchemaOutcome, EngineError>
Set a mem’s schema pin — the conformance-gated schema-migration trigger. Behaviour per the pinned contract:
- requested == current pin →
Noop, no state change. - requested != pin, mem integral against the target →
atomic switch (
schema_pin = target, migration state cleared) in one workspace-store write →Switched. - requested != pin, mem NOT integral → enter (or stay in)
dual-pin:
migration_target = target, writes validate against the target from this call on,findingscarries the non-integral entities →MigrationStarted(first call) /MigrationPending(same target re-issued). - re-issued with the in-flight target once every entity is
integral → atomic switch →
Switched.
The trigger is a label change gated by the conformance check — no content hashing. The response hands the agent findings and nothing else (no migration scripts, no hints); each repair write is validated strictly against the target.
Sourcepub fn export_markdown(
&self,
mem_filter: Option<&str>,
schema_filter: Option<&str>,
) -> Result<ExportResult, EngineError>
pub fn export_markdown( &self, mem_filter: Option<&str>, schema_filter: Option<&str>, ) -> Result<ExportResult, EngineError>
Regenerate entity markdown files from the in-memory store.
Dispatch:
- When
mem_filterisSome(name), only that mem’s mount is considered. If its active backend doesn’t support markdown regeneration in place (today: anything other thanMountStorage::Folder), the call refuses withEngineError::MarkdownExportUnsupportedBackendcarrying the active backend’s id and the supported-backend list. - When
mem_filterisNone, every mount is iterated. Folder mounts regenerate as today; non-folder mounts are recorded incrate::ops::ExportResult::skipped_mountsso the caller can surface the partial-success shape.
Per-folder-mount behaviour: iterate the store, regenerate each non-stub entity belonging to the mount’s mem, compare to the on-disk file, write if changed.
schema_filter narrows the per-entity-type subset: when
Some(name), only entities whose entity_type matches are
regenerated. None exports every type.
Pre-fix this returned
ExportResult { written: 0, unchanged: 0 } for git-branch /
archive mounts — a successful-looking no-op that masked the
backend-incompatibility. The typed refusal (per-mem) and the
skipped_mounts channel (workspace-wide) give the caller an
agent-actionable signal in one round-trip.
Sourcepub fn export_mem(
&self,
mem_name: &str,
output_path: &Path,
) -> Result<MemExportResult, EngineError>
pub fn export_mem( &self, mem_name: &str, output_path: &Path, ) -> Result<MemExportResult, EngineError>
Export a mem as a portable .mem archive.
Dispatch is internal: the engine looks up the mount whose mem
name matches and branches on its MountStorage. Folder mounts
produce a snapshot archive (current .md files + config);
git-branch mounts invoke the registered GitBranchOps::export
hook to produce a history archive (the per-mem branch tip’s
tree); archive mounts reject with BackendError::Sealed
(already-an-archive — no meaningful re-export).
The mem’s MemConfig is looked up via
Self::mem_config_for; unloaded configs (folder mounts
without a .memstead/config.json, git-branch mounts without a
__MEMSTEAD:mems/<mem>/config.json) surface as
EngineError::InvalidInput. Workspace-level schema dir is
threaded from self.settings.schemas_dir for the
schema-source resolution chain.
Sourcepub fn set_mem_version(
&mut self,
mem_name: &str,
new_version: Version,
note: Option<&str>,
) -> Result<SetMemVersionOutcome, EngineError>
pub fn set_mem_version( &mut self, mem_name: &str, new_version: Version, note: Option<&str>, ) -> Result<SetMemVersionOutcome, EngineError>
Update a mem’s version field in its per-mem config and
persist it through the backend. Backend-symmetric: folder
backends rewrite .memstead/config.json; git-branch backends
commit __MEMSTEAD:mems/<mem>/config.json. Archive mounts
reject with BackendError::Sealed.
Returns the (mem, old_version, new_version) triple so
callers can surface the change without an extra read. Reads
the current value from the in-memory MemConfig and
updates it on success, keeping the next call free of a
stale-version read.
EngineError::UnknownMem when the name resolves to no
mount; EngineError::ReadOnlyMount when the mount is sealed
for writes; EngineError::InvalidInput when the mount has no
loaded MemConfig (folder mount with no
.memstead/config.json; the residual missing-config path is
distinct from the missing-version path). F1.
Sourcepub fn set_mem_description(
&mut self,
mem_name: &str,
new_description: Option<String>,
note: Option<&str>,
) -> Result<SetMemDescriptionOutcome, EngineError>
pub fn set_mem_description( &mut self, mem_name: &str, new_description: Option<String>, note: Option<&str>, ) -> Result<SetMemDescriptionOutcome, EngineError>
Update a mem’s description field in its per-mem config and
persist it through the backend — the one-line text mem-archive
export embeds and the registry card surfaces. None clears the
field. Same backend symmetry, drift probe, and provenance-note
posture as Self::set_mem_version; archive mounts reject with
BackendError::Sealed.
Sourcepub fn set_mem_sync_state(
&mut self,
mem_name: &str,
key: &str,
token: &str,
note: Option<&str>,
) -> Result<SetMemSyncStateOutcome, EngineError>
pub fn set_mem_sync_state( &mut self, mem_name: &str, key: &str, token: &str, note: Option<&str>, ) -> Result<SetMemSyncStateOutcome, EngineError>
Set (or clear) one opaque sync-state token in a mem’s per-mem config and persist it through the backend. The ingest layer calls this after a successful pass over a source’s changed slice to record “the source state the graph was last synced against”.
key and token are both opaque to the engine: the key is
conventionally "<ingest>/<facet>" but the engine treats it as an
arbitrary string; the token’s meaning belongs to the medium-type
layer (git → commit id, graph → snapshot token, filesystem → a
JSON-stringified stat digest). The engine never parses either.
An empty token removes the key — the surface for clearing a
baseline (which the next ingest pass re-seeds at the current
source state).
Backend-symmetric like Self::set_mem_version: folder backends
rewrite .memstead/config.json; git-branch backends commit
__MEMSTEAD:mems/<mem>/config.json. Archive mounts reject with
BackendError::Sealed.
Returns the (mem, key, previous-token) triple so callers can
surface the change without an extra read. EngineError::UnknownMem
when the name resolves to no mount; EngineError::ReadOnlyMount
when the mount is sealed for writes; EngineError::InvalidInput
when the mount has no loaded MemConfig.
Sourcepub fn reload_one_mem(&mut self, mem: &str) -> Result<ReloadResult, EngineError>
pub fn reload_one_mem(&mut self, mem: &str) -> Result<ReloadResult, EngineError>
Re-read the named mount’s backend entities and refresh the
in-memory store for that mem. Returns the diff against the
pre-reload snapshot — added (ids newly present), removed
(ids no longer present), changed (same id, different
content_hash).
Operator-triggered: useful when an external writer modified disk while this engine instance was alive (the lean flavour assumes single-writer; this primitive is the escape hatch when that assumption breaks). On the happy path the diff is empty.
Drift detection (whether disk did change) is not part of this
surface — callers that want to short-circuit on “nothing
changed” must compare added.is_empty() && changed.is_empty() && removed.is_empty() against the result. Backend-specific
drift signals (git HEAD comparison, mtime check) live in the
full-flavour engine where they have meaning.
Invalidates community + search-index memos on success.
Sourcepub fn reload_one_mem_report(
&mut self,
mem: &str,
) -> Result<ReloadReport, EngineError>
pub fn reload_one_mem_report( &mut self, mem: &str, ) -> Result<ReloadReport, EngineError>
Rich-shape variant of Self::reload_one_mem that returns a
crate::ops::ReloadReport (mem + head_before + head_after +
entities_loaded + changed_entity_ids) instead of the slim
crate::ops::ReloadResult. Handler-facing wrapper consumed
by the memstead_reload MCP tool — the rich shape is the wire
contract MCP callers depend on; the slim form stays for
programmatic consumers that just want the diff lists.
head_before is the engine’s prior cursor for this mem
(its cached last_known_head), not the current on-disk tip:
when a sibling has committed since, the tip has already advanced,
so reporting it would make the advertised
changes_since(since=head_before) recipe span an empty range.
head_after is the freshly-peeled tip from
crate::backend::MemBackend::current_head; the reload also
advances the cursor to it, so a follow-up staleness probe does
not re-reload the same window. Backends without history (folder,
archive) carry no cursor and return Ok(None); both fields fall
back to crate::ops::EMPTY_TREE_SHA for wire-shape stability.
entities_loaded is the post-reload non-stub count for the
mem — same semantic as full’s report.
changed_entity_ids is the union of added ∪ changed ∪ removed from the underlying crate::ops::ReloadResult
so callers don’t have to merge three lists themselves —
matches full’s bundled wire shape.
Sourcepub fn reload_each_writable_mem_reports(
&mut self,
) -> Result<Vec<ReloadReport>, EngineError>
pub fn reload_each_writable_mem_reports( &mut self, ) -> Result<Vec<ReloadReport>, EngineError>
Batched rich-shape variant — returns one
crate::ops::ReloadReport per mounted mem in declaration
order. Counterpart to Self::reload_each_writable_mem
(slim) that the memstead_reload MCP tool’s no-mem path
consumes.
Also re-reads .memstead/workspace.toml and refreshes
crate::workspace::WorkspaceSettings before sweeping the
mems — this is the pairing with the CLI’s
memstead workspace allow-create / grant-cross-link / set-mutations
family. Without this re-read, a CLI write would land on disk but
the running MCP would still serve the engine’s boot-time policy
snapshot; every subsequent memstead_mem_create against the new
allowlist would fail with MEM_PATH_NOT_ALLOWED until process
restart. The workspace-wide form runs the heavier path; the
per-mem form (reload_one_mem_report) intentionally skips
the workspace re-read — content drift doesn’t imply policy
drift.
Reload of workspace.toml is best-effort: a missing or
unparseable file leaves the existing settings untouched. The
per-mem sweep is the primary contract — settings refresh is
the additive bonus.
First-error-aborts: if any mem’s reload fails, the loop stops and the error propagates. Mems reloaded before the failing one are already mutated in the store; the returned error has no rollback. Operators run the per-mem form to retry the failing mem explicitly.
Sourcepub fn reload_each_writable_mem(
&mut self,
) -> Result<Vec<(String, ReloadResult)>, EngineError>
pub fn reload_each_writable_mem( &mut self, ) -> Result<Vec<(String, ReloadResult)>, EngineError>
Reload every mounted mem in declaration order; returns one
(mem, ReloadResult) per mount.
Failure model is first-error-aborts: if any mem’s reload fails, the loop stops and the error propagates. Mems reloaded before the failing one are already mutated in the store; the returned error has no rollback. Operators run the per-mem form to retry the failing mem explicitly.
Caller-friendly batching wrapper around Self::reload_one_mem;
internal cache invalidation happens once per mem (the inner
call invalidates) so an N-mem batch invalidates the memos
N times. That’s wasteful for large workspaces; once the
memstead_reload MCP handler migrates we can tighten this to one
invalidation at the end.
Source§impl Engine
impl Engine
Sourcepub fn create_entity(
&mut self,
args: CreateEntityArgs,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<CreateEntityOutcome, EngineError>
pub fn create_entity( &mut self, args: CreateEntityArgs, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<CreateEntityOutcome, EngineError>
Create a new entity in args.mem. Six concerns wired here
in one shape regardless of which backend serves the mount:
- Capability gating — rejects mounts with
ReadOnlycapability before reaching the backend. - Validator pipeline —
validate_section_keys+parse_metadata_valueenforce the pinned schema’s strictness; typedValidationErrorlifts toEngineError::Validation. - Provenance — a
Provenancerecord routes throughbackend.append_provenance(folder writes JSONL, git-branch no-ops since the commit subject + trailers carry the same fields). - Write + commit atomicity —
backend.write_entitythenbackend.commitwith the canonicalmemstead: create <id>subject so the git-branch backend’sread_provenancecan recover the kind. - Store update — re-parse the freshly-generated markdown
so the in-memory
Storemirrors disk (including generator-determinedcontent_hash). - Error envelope —
BackendError::Sealedlifts via theBackendvariant so MCP callers see the typed payload intact;HashMismatchpropagates likewise.
Sourcepub fn create_entity_with_ctx(
&mut self,
args: CreateEntityArgs,
ctx: &CommitContext<'_>,
) -> Result<CreateEntityOutcome, EngineError>
pub fn create_entity_with_ctx( &mut self, args: CreateEntityArgs, ctx: &CommitContext<'_>, ) -> Result<CreateEntityOutcome, EngineError>
CommitContext-bundling wrapper around Self::create_entity.
Destructures CommitContext into (actor, client, note)
and delegates.
Source§impl Engine
impl Engine
Sourcepub fn classify_delete_referrers(&self, id: &EntityId) -> DeleteReferrers
pub fn classify_delete_referrers(&self, id: &EntityId) -> DeleteReferrers
Classify an entity’s incoming referrers by the source mount’s
capability — the read-only core of the delete guard. Write-Mem
referrers block the delete; ReadOnly referrers trigger the
residual-stub demotion. Per-source dedup collapses an N-edge
source into one ReferrerInfo carrying every rel-type. A
referrer in an unmounted mem is treated as Write
(safe-by-default: refuse rather than silently demote).
Pure read — no disk, commit, lock, or store mutation. Used by
Self::delete_entity and the CLI delete --dry-run preview so
both compute the same verdict from one implementation.
Sourcepub fn delete_entity(
&mut self,
args: DeleteEntityArgs,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<DeleteEntityOutcome, EngineError>
pub fn delete_entity( &mut self, args: DeleteEntityArgs, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<DeleteEntityOutcome, EngineError>
Delete an entity from its mount.
Binary semantics — there is no force flag. The engine partitions
incoming references by the source mem’s
MountCapability:
- any Write-Mem referrers → refuse with typed
EngineError::HasIncomingRefscarrying the structured referrer list; - only ReadOnly-mount referrers → delete the file +
commit, then demote the in-memory entity to a stub at the
same id so the surviving incoming edges keep a valid target.
A
RESIDUAL_STUB_FOR_READONLY_REFERRERSwarning rides on the outcome; - no referrers → clean removal (file + store entry + cascading edges). Orphaned stubs whose last incoming edge was the deleted entity are GC’d.
Optimistic-locking via args.expected_hash matches
update_entity. Stubs (no on-disk file) skip the backend
write and commit but still log provenance.
Sourcepub fn delete_entity_with_ctx(
&mut self,
id: &EntityId,
expected_hash: &str,
ctx: &CommitContext<'_>,
) -> Result<DeleteEntityOutcome, EngineError>
pub fn delete_entity_with_ctx( &mut self, id: &EntityId, expected_hash: &str, ctx: &CommitContext<'_>, ) -> Result<DeleteEntityOutcome, EngineError>
Positional + CommitContext wrapper around
Self::delete_entity. Bundles id + expected_hash into
a DeleteEntityArgs.
Source§impl Engine
impl Engine
Sourcepub fn apply_parse_recovery(
&mut self,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<ParseRecoveryReport, EngineError>
pub fn apply_parse_recovery( &mut self, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<ParseRecoveryReport, EngineError>
Walk load_warnings, dispatch the remove_explicit_relation
recovery for every writable-origin PARSED_RELATION_INVALID,
and report each entry on the response. Read-only-origin
warnings cannot be acted on (the engine has no write access
to their source markdown) and surface as
outcome: "skipped" with reason: "readonly_mount".
Failure model: per-entry failures land on the response as
outcome: "failed" with the underlying engine error code in
reason. The bulk-fix continues past per-entry failures so a
single bad source doesn’t strand the rest of the batch. Only
engine-level errors (reload failure, broken workspace state)
abort the call and propagate via Err.
Idempotency: after the per-source re-renders land, the method
runs reload_each_writable_mem so subsequent calls to
health / load_warnings reflect the post-recovery state.
Re-running on an already-clean workspace returns an empty
entries list with no commits.
Source§impl Engine
impl Engine
Sourcepub fn relate_entity(
&mut self,
args: RelateEntityArgs,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<RelateEntityOutcome, EngineError>
pub fn relate_entity( &mut self, args: RelateEntityArgs, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<RelateEntityOutcome, EngineError>
Add or remove a typed relationship on args.source.
Cross-mem relate is policy-gated through
Engine::cross_mem_link_allowed — the workspace’s
[cross_mem_links] table (or per-create-rule
default_cross_links synthesis) decides whether the edge is
permitted. Disallowed pairings surface
EngineError::CrossMemLinkNotAllowed. Cross-mem relate
only writes the source entity’s markdown — the target mem is
never written to. Auto-stub for absent targets works for
Write target mems; ReadOnly target mems reject absent
targets with EngineError::CrossMemTargetNotFound because
the engine cannot persist a stub through the read-only
boundary.
Schema-undeclared rel types surface either as validation errors (strict mode) or as ride-along warnings on the outcome (open mode).
Sourcepub fn relate(
&mut self,
from: &EntityId,
to: &EntityId,
rel_type: &str,
remove: bool,
ctx: &CommitContext<'_>,
) -> Result<RelateEntityOutcome, EngineError>
pub fn relate( &mut self, from: &EntityId, to: &EntityId, rel_type: &str, remove: bool, ctx: &CommitContext<'_>, ) -> Result<RelateEntityOutcome, EngineError>
Positional-args alias for Self::relate_entity. Bundles
the positional inputs into a RelateEntityArgs (with
expected_hash: None) and delegates to
Self::relate_entity. The CommitContext is destructured
into the 4-tuple (actor, client, note) the unified mutation
surface accepts.
Source§impl Engine
impl Engine
Sourcepub fn rename_entity_with_ctx(
&mut self,
old_id: &EntityId,
new_title: &str,
expected_hash: &str,
ctx: &CommitContext<'_>,
) -> Result<RenameEntityOutcome, EngineError>
pub fn rename_entity_with_ctx( &mut self, old_id: &EntityId, new_title: &str, expected_hash: &str, ctx: &CommitContext<'_>, ) -> Result<RenameEntityOutcome, EngineError>
Positional + CommitContext wrapper around
Self::rename_entity.
Sourcepub fn rename_entity(
&mut self,
args: RenameEntityArgs,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<RenameEntityOutcome, EngineError>
pub fn rename_entity( &mut self, args: RenameEntityArgs, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<RenameEntityOutcome, EngineError>
Rename an entity by changing its title — the slug, id, and on-disk file path follow.
Same-mem referrers and self-references are rewritten
atomically. The renaming entity is treated as the first
referrer of itself: every entry in its own relationships
list whose target equals the old id is updated to point at
the new id, and every [[<old-slug>]] token in its own
section bodies is rewritten to the new slug (respecting
fenced-code and inline-code masking). Every other entity in
the same mem that pointed at the old id (via an explicit
relation or an inline body wiki-link) gets the same two-
surface rewrite. All rewrites land in one per-mem commit.
Cross-mem referrers and ReadOnly-mount referrers are not
yet walked — those land with the multi-mem atomicity
machinery and the residual-stub demotion path respectively.
Source§impl Engine
impl Engine
Sourcepub fn update_entity(
&mut self,
args: UpdateEntityArgs,
actor: Actor,
client: Option<&ClientId>,
note: Option<&str>,
) -> Result<UpdateEntityOutcome, EngineError>
pub fn update_entity( &mut self, args: UpdateEntityArgs, actor: Actor, client: Option<&ClientId>, note: Option<&str>, ) -> Result<UpdateEntityOutcome, EngineError>
Update an entity’s sections and/or metadata.
Same six-concern shape as Engine::create_entity. Optimistic
locking via args.expected_hash: when Some, must match the
store’s current content_hash or returns
EngineError::HashMismatch. The new engine’s MCP-facing
callers should always pass the hash; None is the
--force-style escape hatch.
Internally a two-step pipeline: Self::prepare_update runs
all validation and computes the post-mutation markdown without
committing, then Self::commit_prepared_update stages and
commits the result. The split lets Self::batch_update
prepare every item up front and commit the whole batch as one
atomic unit.
Sourcepub fn batch_update(
&mut self,
updates: Vec<(UpdateEntityArgs, Option<String>)>,
actor: Actor,
client: Option<&ClientId>,
) -> Result<BatchResult, EngineError>
pub fn batch_update( &mut self, updates: Vec<(UpdateEntityArgs, Option<String>)>, actor: Actor, client: Option<&ClientId>, ) -> Result<BatchResult, EngineError>
Apply a batch of UpdateEntityArgs atomically — all or
nothing. Surfaces BatchResult for memstead batch-update
consumers.
The batch validates and prepares every item first (each with
its own optimistic-lock check), then commits the whole set as
one commit per mem. If any item fails — validation error,
HASH_MISMATCH, entity-not-found, any per-item refusal —
nothing is committed: the on-disk mem and the in-memory
store are restored to exactly their pre-call state, and the
result is marked applied: false with the offending item
carrying a typed {code, message, details} error envelope and
every other item marked "not_applied". The first failing item
stops preparation (fail-fast); the caller fixes it and
resubmits.
On success the returned commit_sha is the single batch commit
— an honest memstead_changes_since cursor / revert handle. Each
item’s per-entry note rides into its own provenance record.
Empty batches return applied: true with zero counts and no
commit. A batch where every item is a no-op (content unchanged)
likewise applies with an empty commit_sha.
Atomicity is per-mem: for the common single-mem batch a commit-time backend failure rolls the whole batch back. A batch spanning multiple mems commits each mem in turn; if a later mem’s commit fails, already-committed mems stay committed (true cross-mem two-phase commit is out of scope) — but the dominant failure mode, a per-item validation/hash refusal, is always fully atomic because no commit happens until every item has passed.
Sourcepub fn update_entity_with_ctx(
&mut self,
args: UpdateEntityArgs,
ctx: &CommitContext<'_>,
) -> Result<UpdateEntityOutcome, EngineError>
pub fn update_entity_with_ctx( &mut self, args: UpdateEntityArgs, ctx: &CommitContext<'_>, ) -> Result<UpdateEntityOutcome, EngineError>
CommitContext-bundling wrapper around Self::update_entity.
See Self::create_entity_with_ctx for the rationale.
Source§impl Engine
impl Engine
Sourcepub fn store(&self) -> &Store
pub fn store(&self) -> &Store
In-memory store populated at construction time from every mount’s backend. Read-only at this point in the rebuild — mutation paths land in a later session.
Sourcepub fn schemas(&self) -> &HashMap<String, Arc<Schema>>
pub fn schemas(&self) -> &HashMap<String, Arc<Schema>>
Per-mem schema, keyed by mount’s mem name. Each entry is the schema resolved from that mount’s pin at boot, so the map holds genuinely heterogeneous schemas in a multi-schema workspace.
Sourcepub fn workspace_schemas(&self) -> &[Arc<Schema>]
pub fn workspace_schemas(&self) -> &[Arc<Schema>]
Workspace-authored schemas loaded from
WorkspaceSettings.schemas_dir at construction. Distinct from
Self::schemas (per-mem, only schemas pinned by a mount):
this slice carries every workspace-loaded schema regardless of
whether a mem pins it. Used by memstead_overview to enumerate
schemas referenced by mem_create_rules.schemas[] but not
pinned by any mem — agents see what could be pinned without
looking up the workspace.toml directly.
Sourcepub fn builtin_schemas(&self) -> &[Arc<Schema>]
pub fn builtin_schemas(&self) -> &[Arc<Schema>]
Embedded built-in schemas loaded once at boot. Handlers
resolving a schema pin by <name>@<version> (MCP’s memstead_schema,
memstead_overview rendering) walk mem-pinned, workspace, and
built-in catalogues in order — built-ins are the catch-all when
no mem or workspace dir pins the schema. Workspace schemas
shadow built-ins on (name, version) collision; resolve from
workspace_schemas() first.
Sourcepub fn schema_origin(&self, schema: &Arc<Schema>) -> OriginClass
pub fn schema_origin(&self, schema: &Arc<Schema>) -> OriginClass
Classify a schema’s trust origin — the single authority every read surface consults before serving a schema’s instruction-prose.
A schema is [OriginClass::FirstParty] iff it is an engine built-in
or pinned by a writable mount in this workspace. Built-ins are
compiled into the binary — unforgeable. A non-built-in schema earns
first-party status only once the operator adopts it by writably
mounting a mem that pins it: writing into a mem is the act that
legitimately needs a schema’s authoring prose (system_message,
write_rules, …), and the mount’s writable posture is set by the
consumer’s own config — a publisher cannot forge it.
Everything else is [OriginClass::ThirdParty]: a schema present in
the catalogue but pinned only by read-only mounts (a registry-
installed read-mem or an adopted foreign folder/clone), or one the
engine cannot vouch for at all. Its prose is served structural-only
so a stranger’s free-text never reaches a consuming agent as
instructions. This classifies by the mount graph — never by scanning
the schema’s content, which a publisher controls — and ThirdParty
is the safe default for any ambiguous origin.
Note a read-only mount pinning a built-in schema (e.g. a registry
mem on default@1.0.0) resolves to the consumer’s own clean copy
and stays first-party — the de-framing targets only foreign,
non-built-in schemas that no writable mem has adopted.
Sourcepub fn mem_origin_class(&self, mem: &str) -> OriginClass
pub fn mem_origin_class(&self, mem: &str) -> OriginClass
Classify a mem’s data trust origin — the authority every read
surface consults before serving an entity’s content (bodies,
snippets, titles). A writable mount is [OriginClass::FirstParty]:
its content is authored in this workspace. Anything else — a
read-only mount (a registry-installed read-mem or an adopted
foreign folder/clone) or an unknown mem — is
[OriginClass::ThirdParty], so the consuming agent/host treats the
content as quoted, untrusted data.
This reads the mount’s already-decided writable/read-only posture
(fixed at adopt/mount time) — it never scans content, and the class
is set by the consumer’s mount config, so a publisher cannot forge
first-party. Distinct from Self::schema_origin, which governs a
schema’s instruction-prose: the data channel and the
instruction channel are separate vectors with separate authorities.
Sourcepub fn load_errors(&self) -> &[(PathBuf, String)]
pub fn load_errors(&self) -> &[(PathBuf, String)]
Per-file errors collected during load. Non-fatal: the engine continues with whatever did parse. Empty when every backend’s content parses cleanly.
Sourcepub fn settings(&self) -> &WorkspaceSettings
pub fn settings(&self) -> &WorkspaceSettings
Workspace-level operator policy (mem create/delete rules,
cross-mem links). Defaults to empty; populated via
Engine::set_settings after construction. Surfaced for MCP
handlers (memstead_health { include_config: true },
memstead_overview’s lifecycle-namespaces section) and other
consumers that need to read workspace policy.
Sourcepub fn pipeline_configs(&self) -> &PipelineConfigs
pub fn pipeline_configs(&self) -> &PipelineConfigs
The pipeline configs (Medium / Facet / Projection / Ingest) loaded from the workspace store at boot — the read-only queryable surface the loader exposes. Empty for engines not booted from a workspace root, or for a workspace that declares no pipelines. The ingest skill, future MCP tools, and the macOS app consume this structured form rather than re-reading the JSON folders.
Sourcepub fn pipeline_configs_json(&self) -> String
pub fn pipeline_configs_json(&self) -> String
The pipeline configs serialized as a JSON string — the read
counterpart of the add_*_json edit entry points. Serialization-
boundary callers (UniFFI, where serde does not live) get the
four-primitive store in one call and deserialize on their side.
Shape: { "mediums": [{ mem, name, config }], "facets": [...], "projections": [...], "ingests": [{ name, config }] }. Serializing
these plain records cannot fail; an empty store still yields the
fallback empty object.
Sourcepub fn set_pipeline_configs(&mut self, configs: PipelineConfigs)
pub fn set_pipeline_configs(&mut self, configs: PipelineConfigs)
Overwrite the in-memory pipeline configs. The workspace-root boot
paths call this after crate::pipeline_store::load_pipeline_configs;
exposed so the full boot helper (a separate crate) can populate the
same surface.
Sourcepub fn note_missing_warning(
&self,
tool: &str,
note: Option<&str>,
) -> Option<WarningHint>
pub fn note_missing_warning( &self, tool: &str, note: Option<&str>, ) -> Option<WarningHint>
Build a WarningHint::NoteMissing when the workspace has
[mutations].require_notes = true and the caller omitted (or
passed a blank/whitespace-only) note; None otherwise.
This is the single enforcement point for the require_notes
provenance nudge. Every mutation that accepts a note calls it
on its commit-landing path and pushes the result onto the
outcome’s warnings, so both the CLI and the MCP transports
inherit identical behaviour from the engine response rather than
each re-deriving the policy at its own boundary (the drift that
left the policy decorative on the CLI). tool becomes the
warning’s details.tool — callers pass the engine-level verb
(create_entity, update_entity, relate_entity,
delete_entity, rename_entity, create_mem,
delete_mem), matching the commit Tool: provenance trailer.
The mutation still commits — the policy nudges, it never blocks.
Sourcepub fn backend_factory(&self) -> BackendFactory
pub fn backend_factory(&self) -> BackendFactory
Backend factory currently installed on this engine. Returned by
value because BackendFactory is a function pointer (Copy).
Used by [crate::mem_management::create_mem] to materialise
the backend for a freshly-registered mount; consumers that need
to instantiate a backend ad-hoc can call this directly.
Sourcepub fn git_branch_ops(&self) -> Option<GitBranchOps>
pub fn git_branch_ops(&self) -> Option<GitBranchOps>
Git-branch ops bundle currently installed on this engine.
None on lean-flavor engines that don’t see mem-repo
mounts. Returned by value because super::GitBranchOps is
Copy. create_mem reaches for
the bundle to drive prune_residue against an unmounted
gitdir when the ForceOverwrite recovery action is selected.
Sourcepub fn get_entity(&self, id: &EntityId) -> Option<&Entity>
pub fn get_entity(&self, id: &EntityId) -> Option<&Entity>
Convenience: look up a parsed entity by id. Returns None for
unknown ids, including stub entries created for unresolved
inline-link targets — callers that want to distinguish real
from stub branch on Entity::stub.
Sourcepub fn mem_names(&self) -> Vec<&str>
pub fn mem_names(&self) -> Vec<&str>
Mem names the engine knows about, in declaration order. Cheap; useful for callers that need to enumerate before dispatching by mem.
Sourcepub fn mount(&self, mem: &str) -> Option<&Mount>
pub fn mount(&self, mem: &str) -> Option<&Mount>
Public-shape mount record for mem, or None for an unknown
mem.
Surfaces the operator-facing
crate::workspace::Mount (mem name, schema pin, storage
reference, capability, lifecycle, cross_linkable) so MCP / CLI
handlers can branch on backend-specific shapes via
crate::workspace::MountStorage when they need accessors
that don’t make sense on every backend (e.g. gitdir / branch
for memstead_health { include_config: true }’s git-class
payload). Backends that want the equivalent of full’s
engine.gitdir_for(mem) match
engine.mount(mem).map(|m| &m.storage) against
MountStorage::GitBranch { gitdir, branch } and walk
directly — keeps the engine surface backend-neutral.
Counterpart to Self::mem_names which lists every mount.
Sourcepub fn orphans_by_schema(
&self,
orphan_ids: &[EntityId],
) -> BTreeMap<String, usize>
pub fn orphans_by_schema( &self, orphan_ids: &[EntityId], ) -> BTreeMap<String, usize>
Orphan count attributed to each mem’s pinned schema, over the
given orphan_ids (the caller pre-filters them by any mem scope).
Lets a health surface show that ingest-mem isolates (orphans by
design) and code-mem debt land in different schema buckets rather
than one blended, misleading total. Mems with no settled pin
bucket under the empty string.
Sourcepub fn communities_by_schema(&self, mems: &[String]) -> BTreeMap<String, usize>
pub fn communities_by_schema(&self, mems: &[String]) -> BTreeMap<String, usize>
Community count attributed to each schema across mems: a cluster
counts toward every schema whose mems it touches, so these figures
can sum above the global community count — the same “touches”
semantic as the mem-scoped count. Per-schema dedup keeps a cluster
touching two mems of one schema from being counted twice.
Sourcepub fn mounts(&self) -> Vec<&Mount>
pub fn mounts(&self) -> Vec<&Mount>
All mounts the engine knows about, in declaration order.
Counterpart to Self::mem_names when the caller needs
the full mount shape (e.g. to enumerate by storage variant).
Sourcepub fn writable_mem_names(&self) -> Vec<&str>
pub fn writable_mem_names(&self) -> Vec<&str>
Names of mems whose mount declares
crate::workspace::MountCapability::Write, in declaration
order. Convenience over mounts().iter().filter(...).map(...)
for handlers that gate by writable status (memstead_health,
memstead_overview‘s mem roster, the lifecycle tools’
candidate list). Read-only mounts (archive backends) are
excluded.
Sourcepub fn default_writable_mem(&self) -> Option<&str>
pub fn default_writable_mem(&self) -> Option<&str>
The default writable mem — the target a mutation lands in when
it omits mem. None when no writable mem is mounted.
Defined as the first writable mount in declaration order, i.e.
the seed / earliest-created writable mem. This is a stable
designation, not a function of the current name set: new mems
register via register_writable_mem, which pushes onto the end
of the mount list (and mounts.json preserves that order across
reboots), so creating an additional mem never moves the default
— even one whose name sorts ahead alphabetically. Deleting the
current default promotes the next-earliest writable mem; that is
the only thing that shifts it. Both the MCP resolve_mem and the
CLI’s omitted---mem path resolve through here so the two
surfaces always agree (the
pre-fix MCP path read writable_mems().iter().next() off an
unordered HashSet, which silently retargeted writes when a second
mem appeared).
Sourcepub fn folder_path_for_mem(&self, mem: &str) -> Option<&Path>
pub fn folder_path_for_mem(&self, mem: &str) -> Option<&Path>
On-disk folder path for a folder-backed mount, or None for
any other backend (git-branch, archive) or unknown mem.
Convenience over engine.mount(mem).map(|m| &m.storage) +
matching on MountStorage::Folder { path }. Used by
handlers that need a filesystem path for a folder mem
(e.g. memstead_health { include_config: true }’s
mems[].vcs.worktree field for folder mounts).
Sourcepub fn mem_router(&self) -> &MemRouterSnapshot
pub fn mem_router(&self) -> &MemRouterSnapshot
Runtime snapshot of writable / visible mems. Handlers that
need the writable roster (memstead_health’s writable_mems /
read_mems), per-mem origin tag (include_config: true’s mems[].origin), or visibility check
(memstead_overview‘s mem list, the lifecycle tools’ collision
guard) consume the router here. Returned by reference — the
Arc is held on the engine; callers that need a clonable
handle can Arc::clone the engine’s field directly when that
surface arrives.
Sourcepub fn gitdir_for(&self, mem_name: &str) -> Result<PathBuf, EngineError>
pub fn gitdir_for(&self, mem_name: &str) -> Result<PathBuf, EngineError>
Resolve the gitdir for a writable mem. Used by memstead_health { include_config: true } to surface per-mem vcs.gitdir
so outer-repo auto-commit hooks can git -C <gitdir> per
mem without hardcoding the layout.
EngineError::UnknownMemwhen the name does not resolve.EngineError::Memwhen the mount’s storage is not git-branch-backed (folder, archive — they have no gitdir).
Sourcepub fn worktree_for(&self, mem_name: &str) -> Result<PathBuf, EngineError>
pub fn worktree_for(&self, mem_name: &str) -> Result<PathBuf, EngineError>
Resolve the worktree for a writable mem. Used by
memstead_health { include_config: true } to surface per-mem
vcs.worktree.
EngineError::UnknownMemwhen the name does not resolve.EngineError::Memwhen the mount’s backend has no worktree concept (git-branch with no working tree, archive).
Folder mounts surface their on-disk path. Git-branch mounts
follow the dir: Some(...) composition pattern: when the
workspace root contains a folder named after the mem with a
.memstead/config.json marker, that folder is the worktree
(disk-shape composition). Otherwise — pure mem-repo-backed
— return Err.
Sourcepub fn mem_config_for(&self, mem: &str) -> Option<&MemConfig>
pub fn mem_config_for(&self, mem: &str) -> Option<&MemConfig>
Per-mem .memstead/config.json payload, when available. Used
by memstead_health { include_config: true } to surface the
opaque write_guidance map and the catch-all extra fields
per mem.
Folder-backed mounts return Some(&MemConfig) when
<path>/.memstead/config.json parsed cleanly at construction.
Git-branch and archive backends return None until the
read-from-storage-backend path lifts (the V1 unified engine
loads configs only from folder layouts; the file lives
inside the gitdir / archive for the other backends and
needs a backend-level read primitive).
Unknown mem names return None (no error variant — the
accessor is intentionally lenient because memstead_health emits
an empty detail block per missing config rather than
aborting the call).
Sourcepub fn archive_provenance_for(&self, mem: &str) -> Option<&ArchiveProvenance>
pub fn archive_provenance_for(&self, mem: &str) -> Option<&ArchiveProvenance>
The authoring-provenance payload an installed mem carries, read
from the archive’s .memstead/provenance.json at construction.
None when the mem carries none (a pre-provenance archive, a
runtime-created mem, or a backend that does not surface one) —
the read path reports provenance as absent. Unknown mem names
return None.
Sourcepub fn mem_configs_named(&self) -> impl Iterator<Item = (&str, &MemConfig)>
pub fn mem_configs_named(&self) -> impl Iterator<Item = (&str, &MemConfig)>
Iterate (mem_name, &MemConfig) for every mount whose
mem-config payload loaded at construction. Used by callers
that walk every writable mount’s config (memstead health’s
per-mem dump, the workspace-dump CLI). The yielded &str is
the authoritative mem leaf from the mount record.
Folder-backed mounts yield when their .memstead/config.json
parsed cleanly. Git-branch and archive backends are silent in
V1 (the same deferred-read-from-storage gap that
Self::mem_config_for documents).
Sourcepub fn schema_for(&self, mem: &str) -> Option<Arc<Schema>>
pub fn schema_for(&self, mem: &str) -> Option<Arc<Schema>>
Resolved Arc<Schema> for a writable mem by name. None
when the name is not a registered mount.
Cheap — Arc::clone over the per-mem schema map. Resolved
schemas are stored in HashMap<String, Arc<Schema>> so the
lookup is a single hash hit + clone.
Sourcepub fn mem_head_sha(
&self,
mem_name: &str,
) -> Result<Option<String>, EngineError>
pub fn mem_head_sha( &self, mem_name: &str, ) -> Result<Option<String>, EngineError>
Cached current branch-tip cursor (typically a 40-char hex
SHA for git-branch backends; None for fresh mems or
backends that don’t track a head — folder / archive).
The value is the per-mount last_known_head, seeded at
construction by backend.current_head() and refreshed by
Self::reload_if_stale / mutation paths after a
successful commit.
EngineError::UnknownMemwhen the name does not resolve.
Sourcepub fn mem_drifted(&self, mem_name: &str) -> Result<bool, EngineError>
pub fn mem_drifted(&self, mem_name: &str) -> Result<bool, EngineError>
Whether a sibling writer has advanced this mem’s backend past
the engine’s cached last_known_head — a read-only drift probe
that does not reload (unlike Self::reload_if_stale). One
backend.current_head() read compared against the cached cursor;
the comparison clears once the engine re-reads (a reload /
reload_if_stale refreshes last_known_head to the live tip).
Only git-branch backends track a head, so folder / archive /
in-memory mounts always report false. A backend that errors on
the probe (transient refdb hiccup) reports false rather than
surfacing the error — drift is advisory, and the next real
operation’s reload path is the authoritative sync.
EngineError::UnknownMemwhen the name does not resolve.
Sourcepub fn workspace_root(&self) -> Option<&Path>
pub fn workspace_root(&self) -> Option<&Path>
Workspace root the engine booted from, when one is known.
None for engines built directly from a mount list (tests,
ad-hoc consumers). Set by Self::from_workspace_root and
the full counterpart.
Sourcepub fn load_warnings(&self) -> &[WarningHint]
pub fn load_warnings(&self) -> &[WarningHint]
Typed warnings surfaced during mem load — drift findings the loader pipeline collects per entity. Empty for V1; the accessor surfaces them so handlers can merge into health summaries uniformly.
Sourcepub fn communities(&self) -> &LouvainOutput
pub fn communities(&self) -> &LouvainOutput
Lazy community-detection cache. First call runs Louvain
against the current store using one pinned schema for
community.{resolution, seed} and the per-rel weights.
Subsequent calls return the cached result. Mutations invalidate
the cache via Self::invalidate_communities.
One detection run per engine. The partition is workspace-global, so it needs a single source for the Louvain parameters; that source is the schema of the lexicographically-first mem name — a stable key, so the partition is deterministic across processes even when mounts pin heterogeneous schemas. For a single-schema workspace every mem’s schema is identical, so the choice of key is immaterial there.
Sourcepub fn invalidate_communities(&mut self)
pub fn invalidate_communities(&mut self)
Drop the cached community detection result.
Sourcepub fn most_connected(&self, limit: usize) -> Vec<Connectivity>
pub fn most_connected(&self, limit: usize) -> Vec<Connectivity>
Top limit entities by total degree.
Sourcepub fn missing_required_outgoing(
&self,
mem_filter: Option<&str>,
) -> Vec<MissingRequiredOutgoingReport>
pub fn missing_required_outgoing( &self, mem_filter: Option<&str>, ) -> Vec<MissingRequiredOutgoingReport>
Entities whose type’s required_outgoing blocks are not yet
satisfied. mem_filter = None scans every mem; Some(v)
scans only that mem.
Sourcepub fn conformance_findings(
&self,
mem: &str,
target_schema: Option<&SchemaRef>,
) -> Result<Vec<IntegrityFinding>, EngineError>
pub fn conformance_findings( &self, mem: &str, target_schema: Option<&SchemaRef>, ) -> Result<Vec<IntegrityFinding>, EngineError>
Conformance-axis integrity findings for one mem — which
entities a write would refuse under the effective schema, and
why. target_schema = None lints against the mem’s current
pin; Some(ref) lints against that schema instead (resolved
among mem-pinned, workspace, and built-in schemas).
Sourcepub fn schema_pin(&self, mem: &str) -> Option<SchemaRef>
pub fn schema_pin(&self, mem: &str) -> Option<SchemaRef>
The mem’s Mount.schema expectation assertion, when set.
None for unknown mems and for mems whose mount carries no
assertion (the authoritative pin then lives in the backend
config; the resolved active schema, not this, is the effective pin).
Sourcepub fn migration_target(&self, mem: &str) -> Option<SchemaRef>
pub fn migration_target(&self, mem: &str) -> Option<SchemaRef>
The mem’s in-flight migration target, when dual-pin state is
active. None for settled or unknown mems.
Sourcepub fn consistency_findings(
&self,
mem: &str,
) -> Result<Vec<IntegrityFinding>, EngineError>
pub fn consistency_findings( &self, mem: &str, ) -> Result<Vec<IntegrityFinding>, EngineError>
Consistency-axis integrity findings for one mem — the
pre-existing graph-coherence categories (dangling links, stubs)
projected into the { id, axis, code, detail } finding shape.
Sourcepub fn health(&self) -> HealthSummary
pub fn health(&self) -> HealthSummary
Engine-wide health summary across every mount.
Sourcepub fn stats(&self) -> Stats
pub fn stats(&self) -> Stats
Engine-wide crate::ops::Stats across every mount.
Sourcepub fn context(&self, id: &EntityId) -> Option<ContextResult>
pub fn context(&self, id: &EntityId) -> Option<ContextResult>
Build a ContextResult for id: the community cluster id
(or None when the entity is a stub or not present), plus the
outgoing + incoming neighbour lists.
Sourcepub fn search_indexes(&self) -> &HashMap<String, MemIndex>
pub fn search_indexes(&self) -> &HashMap<String, MemIndex>
Lazily-built per-mem search index map. The map carries one
entry per writable mem. Build cost scales with entity count;
expect hundreds-of-ms for thousand-entity workspaces. Not
available on wasm32 targets — search lives behind the bridge
(see Self::search for the typed refuse).
Sourcepub fn invalidate_search_indexes(&mut self)
pub fn invalidate_search_indexes(&mut self)
Drop the cached per-mem search index map. No-op on wasm32
where no index exists; the method stays present so mutation
hooks can call it unconditionally.
Sourcepub fn list(&self, scope: &SearchScope) -> ListResult
pub fn list(&self, scope: &SearchScope) -> ListResult
Filter the in-memory store by metadata only (no text match).
Sourcepub fn search(&self, scope: &SearchScope) -> Result<SearchResult, EngineError>
pub fn search(&self, scope: &SearchScope) -> Result<SearchResult, EngineError>
Run a search against the lazily-built index map. Returns
EngineError::SearchUnavailable on wasm32 targets — browser
consumers route search to the bridge; the local
engine never builds a tantivy index in WASM. Native targets get
the same shape as before, wrapped in Ok.
Sourcepub fn list_entities(&self, mem: &str) -> Result<Vec<PathBuf>, EngineError>
pub fn list_entities(&self, mem: &str) -> Result<Vec<PathBuf>, EngineError>
All mem-relative entity paths under mem. Delegates to
the backend’s list_entities. Order is backend-defined.
Sourcepub fn read_entity(
&self,
mem: &str,
rel_path: &Path,
) -> Result<Option<Vec<u8>>, EngineError>
pub fn read_entity( &self, mem: &str, rel_path: &Path, ) -> Result<Option<Vec<u8>>, EngineError>
Raw bytes for a single entity (Ok(None) if absent).
Sourcepub fn read_provenance(
&self,
mem: &str,
cursor: Option<&str>,
) -> Result<Vec<Provenance>, EngineError>
pub fn read_provenance( &self, mem: &str, cursor: Option<&str>, ) -> Result<Vec<Provenance>, EngineError>
Provenance entries for mem since cursor. Cursor shape is
backend-specific (RFC-3339 timestamp for folder, commit SHA for
git-branch); None means “from the beginning”.
Sourcepub fn capability(&self, mem: &str) -> Result<MountCapability, EngineError>
pub fn capability(&self, mem: &str) -> Result<MountCapability, EngineError>
Capability declared on the mount for mem. Surfaced for
callers that need to gate before dispatching a write — the
engine itself does not yet enforce capability (mutation paths
land in a later session).
Sourcepub fn edge_is_from_readonly(&self, from: &EntityId) -> bool
pub fn edge_is_from_readonly(&self, from: &EntityId) -> bool
Returns true when from’s source mem is mounted with
crate::workspace::MountCapability::ReadOnly. Returns
false for Write-Mems and for mems whose mount is absent
from the router (no mount → no ReadOnly assertion can be
made; the absence is treated as not-ReadOnly so consumers
don’t trip on transient lookup misses).
Plan body §“Single edge source in the store” specifies this
helper as the derived-on-demand alternative to adding a new
field on crate::store::Edge. Strict-invariant validators
and surfaces that want to highlight cross-mount references
call this rather than pattern-matching on a per-edge marker.
The information is fully derivable from the current mount
roster, so no new state needs to live on the edge itself.
Sourcepub fn cross_mem_link_allowed(&self, from_mem: &str, to_mem: &str) -> bool
pub fn cross_mem_link_allowed(&self, from_mem: &str, to_mem: &str) -> bool
Whether a cross-mem edge from from_mem to to_mem is
permitted under the current crate::WorkspaceSettings
cross-mem link policy.
Resolution rules (matches full’s mem_router semantics):
- Same-mem edge (
from_mem == to_mem) → always allowed; the policy gates cross-mem edges only. - Explicit
cross_mem_links[from_mem]:"*"(wildcard) → allowed regardless of target.["a", ...](allowlist) → allowed iffto_memis in the list.
- Per-create-rule
default_cross_linkssynthesis — if rule (1) didn’t grant permission andfrom_memmatches a[[mem_management.create]]rule whosedefault_cross_linksis set, the synthesised value contributes:"*"→ allowed regardless of target.["a", ...]→ allowed iffto_memis in the list.
- Otherwise → denied (default-deny posture).
The synthesis layer compiles a crate::mem_management::CreateRuleSet
lazily on first call and caches it; Self::set_settings
invalidates the cache. Compilation failure (malformed glob
in a rule) logs a warning and the synthesis layer is silently
skipped — the resolver still returns true from explicit
policy alone, so a half-broken config doesn’t lock out edges
the operator did intend to allow. Operators who want hard
validation pre-compile via
crate::mem_management::CreateRuleSet::new before
calling Self::set_settings.
The MCP memstead_relate handler’s cross-mem gate consumes
this method directly.
Source§impl Engine
impl Engine
Sourcepub fn add_medium(
&mut self,
mem: &str,
name: &str,
medium: &Medium,
) -> Result<(), PipelineEditError>
pub fn add_medium( &mut self, mem: &str, name: &str, medium: &Medium, ) -> Result<(), PipelineEditError>
Create a medium and refresh the in-memory snapshot. See add_medium.
Sourcepub fn update_medium(
&mut self,
mem: &str,
name: &str,
medium: &Medium,
) -> Result<(), PipelineEditError>
pub fn update_medium( &mut self, mem: &str, name: &str, medium: &Medium, ) -> Result<(), PipelineEditError>
Overwrite a medium and refresh the snapshot. See update_medium.
Sourcepub fn delete_medium(
&mut self,
mem: &str,
name: &str,
) -> Result<(), PipelineEditError>
pub fn delete_medium( &mut self, mem: &str, name: &str, ) -> Result<(), PipelineEditError>
Delete a medium and refresh the snapshot. See delete_medium.
Sourcepub fn rename_medium(
&mut self,
mem: &str,
old: &str,
new: &str,
) -> Result<(), PipelineEditError>
pub fn rename_medium( &mut self, mem: &str, old: &str, new: &str, ) -> Result<(), PipelineEditError>
Rename a medium and refresh the snapshot. See rename_medium.
Sourcepub fn add_facet(
&mut self,
mem: &str,
name: &str,
facet: &Facet,
) -> Result<(), PipelineEditError>
pub fn add_facet( &mut self, mem: &str, name: &str, facet: &Facet, ) -> Result<(), PipelineEditError>
Create a facet and refresh the snapshot. See add_facet.
Sourcepub fn update_facet(
&mut self,
mem: &str,
name: &str,
facet: &Facet,
) -> Result<(), PipelineEditError>
pub fn update_facet( &mut self, mem: &str, name: &str, facet: &Facet, ) -> Result<(), PipelineEditError>
Overwrite a facet and refresh the snapshot. See update_facet.
Sourcepub fn delete_facet(
&mut self,
mem: &str,
name: &str,
) -> Result<(), PipelineEditError>
pub fn delete_facet( &mut self, mem: &str, name: &str, ) -> Result<(), PipelineEditError>
Delete a facet and refresh the snapshot. See delete_facet.
Sourcepub fn rename_facet(
&mut self,
mem: &str,
old: &str,
new: &str,
) -> Result<(), PipelineEditError>
pub fn rename_facet( &mut self, mem: &str, old: &str, new: &str, ) -> Result<(), PipelineEditError>
Rename a facet and refresh the snapshot. See rename_facet.
Sourcepub fn add_projection(
&mut self,
mem: &str,
name: &str,
projection: &Projection,
) -> Result<(), PipelineEditError>
pub fn add_projection( &mut self, mem: &str, name: &str, projection: &Projection, ) -> Result<(), PipelineEditError>
Create a projection and refresh the snapshot. See add_projection.
Sourcepub fn update_projection(
&mut self,
mem: &str,
name: &str,
projection: &Projection,
) -> Result<(), PipelineEditError>
pub fn update_projection( &mut self, mem: &str, name: &str, projection: &Projection, ) -> Result<(), PipelineEditError>
Overwrite a projection and refresh the snapshot. See update_projection.
Sourcepub fn delete_projection(
&mut self,
mem: &str,
name: &str,
) -> Result<(), PipelineEditError>
pub fn delete_projection( &mut self, mem: &str, name: &str, ) -> Result<(), PipelineEditError>
Delete a projection and refresh the snapshot. See delete_projection.
Sourcepub fn rename_projection(
&mut self,
mem: &str,
old: &str,
new: &str,
) -> Result<(), PipelineEditError>
pub fn rename_projection( &mut self, mem: &str, old: &str, new: &str, ) -> Result<(), PipelineEditError>
Rename a projection and refresh the snapshot. See rename_projection.
Sourcepub fn add_ingest(
&mut self,
name: &str,
ingest: &Ingest,
) -> Result<(), PipelineEditError>
pub fn add_ingest( &mut self, name: &str, ingest: &Ingest, ) -> Result<(), PipelineEditError>
Create an ingest and refresh the snapshot. See add_ingest.
Sourcepub fn update_ingest(
&mut self,
name: &str,
ingest: &Ingest,
) -> Result<(), PipelineEditError>
pub fn update_ingest( &mut self, name: &str, ingest: &Ingest, ) -> Result<(), PipelineEditError>
Overwrite an ingest and refresh the snapshot. See update_ingest.
Sourcepub fn delete_ingest(&mut self, name: &str) -> Result<(), PipelineEditError>
pub fn delete_ingest(&mut self, name: &str) -> Result<(), PipelineEditError>
Delete an ingest and refresh the snapshot. See delete_ingest.
Sourcepub fn rename_ingest(
&mut self,
old: &str,
new: &str,
) -> Result<(), PipelineEditError>
pub fn rename_ingest( &mut self, old: &str, new: &str, ) -> Result<(), PipelineEditError>
Rename an ingest and refresh the snapshot. See rename_ingest.
Sourcepub fn add_medium_json(
&mut self,
mem: &str,
name: &str,
medium_json: &str,
) -> Result<(), PipelineEditError>
pub fn add_medium_json( &mut self, mem: &str, name: &str, medium_json: &str, ) -> Result<(), PipelineEditError>
Self::add_medium from a JSON-encoded Medium.
Sourcepub fn update_medium_json(
&mut self,
mem: &str,
name: &str,
medium_json: &str,
) -> Result<(), PipelineEditError>
pub fn update_medium_json( &mut self, mem: &str, name: &str, medium_json: &str, ) -> Result<(), PipelineEditError>
Self::update_medium from a JSON-encoded Medium.
Sourcepub fn add_facet_json(
&mut self,
mem: &str,
name: &str,
facet_json: &str,
) -> Result<(), PipelineEditError>
pub fn add_facet_json( &mut self, mem: &str, name: &str, facet_json: &str, ) -> Result<(), PipelineEditError>
Self::add_facet from a JSON-encoded Facet.
Sourcepub fn update_facet_json(
&mut self,
mem: &str,
name: &str,
facet_json: &str,
) -> Result<(), PipelineEditError>
pub fn update_facet_json( &mut self, mem: &str, name: &str, facet_json: &str, ) -> Result<(), PipelineEditError>
Self::update_facet from a JSON-encoded Facet.
Sourcepub fn add_projection_json(
&mut self,
mem: &str,
name: &str,
projection_json: &str,
) -> Result<(), PipelineEditError>
pub fn add_projection_json( &mut self, mem: &str, name: &str, projection_json: &str, ) -> Result<(), PipelineEditError>
Self::add_projection from a JSON-encoded Projection.
Sourcepub fn update_projection_json(
&mut self,
mem: &str,
name: &str,
projection_json: &str,
) -> Result<(), PipelineEditError>
pub fn update_projection_json( &mut self, mem: &str, name: &str, projection_json: &str, ) -> Result<(), PipelineEditError>
Self::update_projection from a JSON-encoded Projection.
Sourcepub fn add_ingest_json(
&mut self,
name: &str,
ingest_json: &str,
) -> Result<(), PipelineEditError>
pub fn add_ingest_json( &mut self, name: &str, ingest_json: &str, ) -> Result<(), PipelineEditError>
Self::add_ingest from a JSON-encoded Ingest.
Sourcepub fn update_ingest_json(
&mut self,
name: &str,
ingest_json: &str,
) -> Result<(), PipelineEditError>
pub fn update_ingest_json( &mut self, name: &str, ingest_json: &str, ) -> Result<(), PipelineEditError>
Self::update_ingest from a JSON-encoded Ingest.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Engine
impl !RefUnwindSafe for Engine
impl !Sync for Engine
impl !UnwindSafe for Engine
impl Send for Engine
impl Unpin for Engine
impl UnsafeUnpin for Engine
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&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
impl<T> DowncastSend for T
impl<T> Fruit for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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