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 record_check(
&mut self,
mem_name: &str,
entity_id: &str,
verdict: Verdict,
method: Option<&str>,
actor: Actor,
client: Option<&ClientId>,
) -> Result<CheckRecord, EngineError>
pub fn record_check( &mut self, mem_name: &str, entity_id: &str, verdict: Verdict, method: Option<&str>, actor: Actor, client: Option<&ClientId>, ) -> Result<CheckRecord, EngineError>
Record a check of one entity. Refuses typed on unknown mem
(quarantine included), unknown entity, read-only mounts, and
on any persistence failure (CHECK_NOT_RECORDED) — recording
is never best-effort, because a caller who believes an
unrecorded check landed is the exact dishonesty this tier
removes. The declared role rides engine session state
(Engine::set_role), same as every mutation.
Sourcepub fn entity_check_state(
&self,
mem_name: &str,
entity_id: &str,
) -> Result<(CheckState, Option<CheckRecord>), EngineError>
pub fn entity_check_state( &self, mem_name: &str, entity_id: &str, ) -> Result<(CheckState, Option<CheckRecord>), EngineError>
Derive one entity’s check state and newest check record.
Refuses typed on unknown mem/entity; an engine with no
workspace root has no check store and honestly derives
never_checked (no recorded checks exist).
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,
expected_head: Option<&str>,
) -> Result<BranchResetOutcome, EngineError>
pub fn branch_reset( &mut self, mem: &str, target_sha: &str, expected_head: Option<&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 branch_reset_stranded_refs(
&self,
mem: &str,
target_sha: &str,
) -> Result<Vec<StrandedCrossMemRef>, EngineError>
pub fn branch_reset_stranded_refs( &self, mem: &str, target_sha: &str, ) -> Result<Vec<StrandedCrossMemRef>, EngineError>
Cross-mem references that a reset of mem to target_sha would
strand: incoming edges from entities in other mems whose target
exists at the current head but would not exist at the target
commit — entities created after the target, or renamed to their
current id after it (the reset re-materialises the old id, so
references to the new id dangle either way).
A read — computes against the live store and the commit history,
moves nothing. The human surface calls this fresh at
confirmation-dialog time and warns before branch_reset. Sorted
(from_id, to_id, rel_type) for stable rendering.
Refusals mirror changes_since: UnknownMem, InvalidCursor
for an unresolvable target_sha, InvalidInput for
non-git-backed mounts.
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 render_due_brief(
&self,
today: &str,
window: &DueWindow,
mem_filter: Option<&str>,
) -> Result<String, String>
pub fn render_due_brief( &self, today: &str, window: &DueWindow, mem_filter: Option<&str>, ) -> Result<String, String>
Render the due-brief: every open entity whose declared due date
falls inside (-∞, today + window], across every mem whose
pinned schema declares the axis (writable and read-only mounts
alike), optionally filtered to one mem. today is an ISO
YYYY-MM-DD string — the caller takes it once per invocation,
tests inject it. A workspace with no declaring schema renders
an honest empty brief, not an error.
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 render_html_export(
&self,
mem: &str,
export_date: &str,
) -> Result<String, EngineError>
pub fn render_html_export( &self, mem: &str, export_date: &str, ) -> Result<String, EngineError>
Render one mem as a single self-contained HTML document.
export_date is an ISO date stamped once in the identity
block — the only environmental input besides the store.
Source§impl Engine
impl Engine
Sourcepub fn entity_provenance(
&self,
mem: &str,
entity_id: &str,
) -> Result<EntityProvenance, EngineError>
pub fn entity_provenance( &self, mem: &str, entity_id: &str, ) -> Result<EntityProvenance, EngineError>
Derive an entity’s provenance block (agent-trust plan 13):
created-by (the oldest recorded touch, only when it IS the
creation) and last-modified-by (the newest touch), each with
actor identity, client, declared role, and timestamp — read
from the same append-only record entity_history serves, so
no verb can alter it after the fact. Pages through the full
story to reach the creation record when histories exceed one
page.
Sourcepub fn entity_history(
&self,
mem: &str,
entity_id: &str,
page_size: Option<usize>,
cursor: Option<&str>,
) -> Result<EntityHistoryReport, EngineError>
pub fn entity_history( &self, mem: &str, entity_id: &str, page_size: Option<usize>, cursor: Option<&str>, ) -> Result<EntityHistoryReport, EngineError>
An entity’s recorded history: every touch, newest-first, with
rename chains followed so the story starts at the entity’s
first appearance under any prior id. Bounded and pageable —
page_size clamps to HISTORY_PAGE_MAX, cursor continues
a prior page (INVALID_CURSOR when it matches no touch).
Refusals: UNKNOWN_MEM, ENTITY_NOT_FOUND (an unknown id
never yields an empty story), INVALID_INPUT on archive
mounts (their seam records no history — refusing beats
fabricating emptiness).
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_mutation_clock(&mut self, clock: MutationClock)
pub fn set_mutation_clock(&mut self, clock: MutationClock)
Replace the mutation-timestamp clock — the source every
engine-stamped metadata field (init_timestamp /
auto_timestamp schema flags: created_date, last_modified)
reads. A testing affordance for suites that assert over
canonical entity bytes (e.g. cross-surface hash parity):
pin both engines to the same constant and byte-level
nondeterminism from wall-clock seconds disappears. Production
code never calls this — the default installed at construction
is the system clock, and the stamped format is unchanged
either way.
Sourcepub fn set_role(&mut self, role: Role)
pub fn set_role(&mut self, role: Role)
Set the caller-declared role for subsequent mutations
(agent-trust plan 13). The surface calls this before every
mutation with the per-call parameter resolved against its
session default (per-call wins); Role::Unspecified records
as absence.
Sourcepub fn current_role(&self) -> Role
pub fn current_role(&self) -> Role
The currently declared role — what the next mutation records.
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 stage_sealed_schema(
&mut self,
mem: &str,
pin: &SchemaRef,
files: &[(String, Vec<u8>)],
) -> Result<SchemaStaging, EngineError>
pub fn stage_sealed_schema( &mut self, mem: &str, pin: &SchemaRef, files: &[(String, Vec<u8>)], ) -> Result<SchemaStaging, EngineError>
Make a sealed third-party schema package resolvable in this workspace — the install-time half of “a published mem installs on the strength of the schema it carries”.
The package (package-relative files: schema.yaml,
types/<t>.yaml, schema-format.json) is written into the
workspace’s local schema storage, the same storage
Self::install_schema writes to and the pin resolver reads —
so the mount that follows resolves without a fourth mechanism
and a second mem pinning the same schema finds it already
there. The loaded schema also lands in this engine’s live
catalogue, so the caller mounts in the same process without a
reload.
Two things it deliberately does NOT do. It does not run the
authoring gate (Self::validate_schema_package): these are a
third party’s sealed bytes, the installing user cannot fix
them, and the archive validator has already admitted them under
the sealed reading — applying the authoring gate here would
make an archive that is valid to publish invalid to install.
And it does not append the format marker: presence of the
marker IS the package’s metadata-polarity generation, so
injecting one would rewrite the meaning of bytes the publisher
sealed.
Idempotent: a pin the engine can already resolve returns
SchemaStaging::AlreadyResolvable with nothing written.
Sourcepub fn validate_schema_package(
name: &str,
version: &str,
files: &[(String, Vec<u8>)],
) -> Result<(), EngineError>
pub fn validate_schema_package( name: &str, version: &str, files: &[(String, Vec<u8>)], ) -> Result<(), EngineError>
Validate a schema package’s files before they are sealed. Runs
the full loader (structural + semantic) plus the section-heading
round-trip gate, and checks the manifest’s declared identity
matches the (name, version) the package is being installed
under — a mismatch would seal the schema under a ref its own
manifest contradicts.
pub so the below-boot install path (memstead-git-branch’s
repair surface) runs the SAME gate as this booted path — the
two must never fork into separate validation regimes.
Sourcepub fn validate_schema_exemplars(schema: &Arc<Schema>) -> Result<(), String>
pub fn validate_schema_exemplars(schema: &Arc<Schema>) -> Result<(), String>
Validate every type exemplar a schema carries by running it
through the REAL create validation stage (agent-trust plan 09):
an in-memory engine is booted with the candidate schema pinned
on a virtual mem, and each exemplar is submitted as a
dry_run create — the same gates a real write runs (sections,
metadata + enums, rel-type vocabulary, edge shape, description
posture), commit-free by construction. Placeholder relation
targets are bare slugs scoped to the virtual mem, so target
existence is never checked (an absent target is the legal
would-be-stub path).
Returns the defect as a message naming the type — the caller
wraps it in its own typed envelope (SchemaPackageInvalid on
the install path). There is deliberately no warn-and-carry
mode: a non-conformant exemplar refuses, because the whole
value of an exemplar is the impossibility of drift.
pub so the built-in suite gates every shipped exemplar
through the SAME validator (a broken built-in exemplar fails
CI), and the below-boot install path shares the gate via
Self::validate_schema_package.
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_read_mount(
&mut self,
mount: Mount,
backend: Box<dyn MemBackend>,
origin: MemOrigin,
) -> Result<(), EngineError>
pub fn register_read_mount( &mut self, mount: Mount, backend: Box<dyn MemBackend>, origin: MemOrigin, ) -> Result<(), EngineError>
Register a read-only mount at runtime — the install path’s
engine primitive. Same registration pipeline as
Self::register_writable_mem (collision probe, config read,
schema resolution, entity load, router swap); the router branch
lands the mount in the read-only slot for
capability: ReadOnly + Archive storage, so is_writable
stays false and archive_path_for_mem resolves.
Sourcepub fn unregister_read_mount(
&mut self,
mem_name: &str,
) -> Result<Option<Box<dyn MemBackend>>, EngineError>
pub fn unregister_read_mount( &mut self, mem_name: &str, ) -> Result<Option<Box<dyn MemBackend>>, EngineError>
Unregister a read-only mount at runtime — the uninstall path’s
engine primitive, mirroring Self::unregister_writable_mem
for the read-only slot. Returns Ok(None) when the name is
not a registered read-only mount (writable mems are the
delete/unregister verbs’ business, deliberately not this
one’s). Registration removal only — the backing archive file
(global cache) is never touched.
Sourcepub fn push_load_warning(&mut self, warning: WarningHint)
pub fn push_load_warning(&mut self, warning: WarningHint)
Append a typed load-time warning from outside the engine’s own load pipeline — the boot orchestrators (which live in the full crate) use this to surface one-time migrations they perform around engine construction.
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 full_refresh(&mut self) -> FullRefreshReport
pub fn full_refresh(&mut self) -> FullRefreshReport
Additive full refresh — the warm-server half of “restart the process”: re-scan the schema sources and the mount manifest, making newly installed schema versions resolvable and newly registered mems usable, WITHOUT applying removals. The asymmetry is deliberate and is the whole safety argument: adding extends what the in-memory store can answer, while removing can strand entities, in-flight handles, and cached hashes the process is still serving. Removals are skipped and reported; a restart applies them.
Failure model is per-item: a schema source or a mount that
fails to refresh lands in failures and never surfaces as
newly available; the others proceed. Each mount registration
is all-or-nothing (every fallible step runs before the store
is touched), so a failed item leaves no half-updated state.
The workspace-global passes (relation validation, alias remap,
memo invalidation) run ONCE per refresh regardless of how many
mounts attached.
A newly mounted mem starts cold and loads like any other
mount. Content reload of pre-existing mems is NOT part of this
method — callers that want both (the memstead_reload full=true surface) run the existing content-reload sweep
alongside.
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 record_pipeline_edit_provenance(
&self,
mem: &str,
kind: &str,
edits: &[(String, Option<Vec<u8>>)],
note: Option<&str>,
verb: &str,
) -> Result<(), BackendError>
pub fn record_pipeline_edit_provenance( &self, mem: &str, kind: &str, edits: &[(String, Option<Vec<u8>>)], note: Option<&str>, verb: &str, ) -> Result<(), BackendError>
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.
Record pipeline-edit provenance through mem’s backend — the
bridge the pipeline-edit block (outside the engine module) uses
to reach a mount’s backend. A mem that isn’t currently mounted
is a successful no-op: pipeline configs may reference unmounted
mems, and provenance is recorded against the mounted set.
pub fn set_mem_version( &mut self, mem_name: &str, new_version: Version, note: Option<&str>, ) -> Result<SetMemVersionOutcome, EngineError>
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_title(
&mut self,
mem_name: &str,
new_title: Option<String>,
note: Option<&str>,
) -> Result<SetMemTitleOutcome, EngineError>
pub fn set_mem_title( &mut self, mem_name: &str, new_title: Option<String>, note: Option<&str>, ) -> Result<SetMemTitleOutcome, EngineError>
Update a mem’s display title — free text, NOT identity: the
mem name stays the sole handle everywhere. None clears it.
Mirrors Self::set_mem_description in backend symmetry,
drift probe, and provenance-note posture.
Sourcepub fn set_mem_subject(
&mut self,
mem_name: &str,
new_subject: Option<MemSubject>,
note: Option<&str>,
) -> Result<SetMemSubjectOutcome, EngineError>
pub fn set_mem_subject( &mut self, mem_name: &str, new_subject: Option<MemSubject>, note: Option<&str>, ) -> Result<SetMemSubjectOutcome, EngineError>
Update a mem’s subject block — scope, method, deliberate
exclusions, published verbatim. None clears the block AS A
UNIT. Mirrors Self::set_mem_description.
Sourcepub fn set_mem_internal(
&mut self,
mem_name: &str,
internal: bool,
note: Option<&str>,
) -> Result<bool, EngineError>
pub fn set_mem_internal( &mut self, mem_name: &str, internal: bool, note: Option<&str>, ) -> Result<bool, EngineError>
Mark (or unmark) a mem as internal — hidden from the default
memstead_overview roster and public projections, while remaining a
real, schema-validated, diffable mem (inspectable when explicitly
scoped by name, and deletable). The ingest process-state redesign
(candidate (b)) flags each ingest/<name> process mem this way so it
does not clutter the roster alongside real content.
Stored as the top-level internal config field (captured by the
flattened extra map). Backend-symmetric like
Self::set_mem_description; EngineError::UnknownMem /
ReadOnlyMount / InvalidInput on the usual failures.
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.
load_warnings semantics ride on the per-mem contract: each
Self::reload_one_mem in the sweep refreshes its own mem’s
slice of the engine-wide accumulator, so a full sweep leaves
the accumulator equivalent to a fresh boot. (Earlier this
variant discarded every reload warning while its slim
counterpart repopulated — the MCP workspace-wide reload could
never clear a stale warning.) On first-error-abort, mems
reloaded before the failure carry refreshed slices and the
rest keep their boot-time entries — no slice is lost.
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 const BATCH_ERROR_REPORT_CAP: usize = 50
pub const BATCH_ERROR_REPORT_CAP: usize = 50
Cap on fully-detailed error envelopes in a refused batch’s
report — bounded reporting for very large failing batches.
Entries beyond the cap still carry action: "error"; the
result’s errors_suppressed counts them. Never a silent
truncation.
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 batch_create(
&mut self,
creates: Vec<(CreateEntityArgs, Option<String>)>,
actor: Actor,
client: Option<&ClientId>,
dry_run: bool,
) -> Result<BatchResult, EngineError>
pub fn batch_create( &mut self, creates: Vec<(CreateEntityArgs, Option<String>)>, actor: Actor, client: Option<&ClientId>, dry_run: bool, ) -> Result<BatchResult, EngineError>
Atomic batch create — the create-side sibling of
Self::batch_update, with one upgrade and one addition:
- Report-all refusal. Every failing entry is identified with
its index and typed
{code, message, details}envelope (the family’s upgraded contract) — bounded atSelf::BATCH_ERROR_REPORT_CAPdetailed envelopes, witherrors_suppressedcounting the rest. A refused batch writes NOTHING: no entity, no edge, no head movement. - Intra-batch references resolve as REAL targets. Every entity in the batch is staged (a skeleton store entry carrying its declared type) before per-entry validation runs, so an edge to a sibling created in the same batch gets full target-type shape validation, no transient stub, and no stub warning — the batch validates as one graph state, cycles included where the schema permits them. Duplicates within the batch are refused in the identity pass.
One workspace load (the caller’s), one commit per touched mem
(subject memstead: batch-create (N entities)), per-entry
provenance notes exactly like batch_update.
Rehearsal (dry_run: true): the FULL validation pass runs —
identity, skeleton staging (so intra-batch references resolve as
real targets, cycles included), per-entry prepare, report-all
refusals — then the batch stops before any write. A legal batch
returns the would-be receipt (applied: true, per-entry
"created" with the prospective ids) with the marker form’s
empty commit_sha; an illegal one returns the same refusal a
real call would. Nothing is written, committed, or stubbed.
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 rewrite_mem_references(
&mut self,
old_mem: &str,
new_mem: &str,
note: Option<&str>,
) -> Result<MemSweepOutcome, EngineError>
pub fn rewrite_mem_references( &mut self, old_mem: &str, new_mem: &str, note: Option<&str>, ) -> Result<MemSweepOutcome, EngineError>
Rewrite every textual reference to mem old_mem so it carries
new_mem instead: cross-mem wiki-links and Relationships
entries in every writable peer mem, full-id self-references
inside old_mem itself, and old_mem’s anchors-sidecar keys
(<old>--<slug> → <new>--<slug>). One commit per affected
mem; unaffected mems get no commit. Idempotent: a second run
finds nothing left to rewrite and commits nothing — which is
exactly what makes an interrupted mem rename completable by
re-issuing it.
Read-only mounts are skipped (no write access); their stale references surface as load-time stubs on the next boot.
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 batch_relate(
&mut self,
relates: Vec<(RelateEntityArgs, Option<String>)>,
actor: Actor,
client: Option<&ClientId>,
dry_run: bool,
) -> Result<BatchResult, EngineError>
pub fn batch_relate( &mut self, relates: Vec<(RelateEntityArgs, Option<String>)>, actor: Actor, client: Option<&ClientId>, dry_run: bool, ) -> Result<BatchResult, EngineError>
Atomic batch relate — the edge-side sibling of
Self::batch_create / Self::batch_update. One list
carrying both additions and removals, applied in order:
each entry validates against the graph state produced by every
prior valid entry (an add followed by a remove of the same edge
nets to no edge; an acyclic check sees edges added earlier in
the same batch). Per-entry shape mirrors what relate accepts.
- All-or-nothing, report-all. A single invalid entry
refuses the whole batch — no edge changes, no head movement —
and the refusal identifies EVERY failing entry with its typed
{code, message, details}envelope, bounded atSelf::BATCH_ERROR_REPORT_CAPwitherrors_suppressedcounting the rest. An entry after a failing one validates against the state as of the prior valid entries, so a dependent entry may cascade — every reported code is still a true refusal of the submitted file. - One commit per touched mem (subject
memstead: batch-relate (N edges)), per-entry provenance notes, exactly like the rest of the family. No-op entries (idempotent re-add / absent remove) report"noop"and produce no write. - Orphan-stub GC runs over every removed edge’s target after
the commit, same predicate as the single-item path (the
collected ids are not part of
BatchResult’s fixed family shape).
Rehearsal (dry_run: true): the FULL in-order validation
pass runs — each entry staged against the state its
predecessors produced, identical refusals, identical report-all
envelope — then the batch stops before any commit and rolls the
staged state back. A legal batch returns the would-be receipt
(per-entry actions, would-be orphan_stubs_removed computed on
the staged state) with the marker form’s empty commit_sha; an
illegal one returns the same refusal a real call would. No
edge, stub, or commit lands.
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>,
dry_run: bool,
) -> Result<BatchResult, EngineError>
pub fn batch_update( &mut self, updates: Vec<(UpdateEntityArgs, Option<String>)>, actor: Actor, client: Option<&ClientId>, dry_run: bool, ) -> 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 EVERY failing item
carrying a typed {code, message, details} error envelope
(the family’s report-all contract — bounded at
Self::BATCH_ERROR_REPORT_CAP detailed envelopes, with
errors_suppressed counting the rest) and every valid item
marked "not_applied", so one repair cycle fixes the file.
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.
Rehearsal (dry_run: true): the FULL per-item validation
pass runs — identical refusals, identical report-all envelope —
then the batch stops before any write or commit. A legal batch
returns the would-be receipt (applied: true, per-entry
actions) with the marker form’s empty commit_sha; an illegal
one returns the same refusal a real call would. Nothing is
staged, committed, or stamped.
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 record_anchor_observed_hashes(
&mut self,
mem_name: &str,
observed: &[ObservedArtifactHash],
note: Option<&str>,
) -> Result<usize, EngineError>
pub fn record_anchor_observed_hashes( &mut self, mem_name: &str, observed: &[ObservedArtifactHash], note: Option<&str>, ) -> Result<usize, EngineError>
Record verify-observed prepared-content hashes onto hash-less
hash-bearing anchors in mem_name’s anchors sidecar — the
measurement-bookkeeping backfill the verify pass hands over via
crate::ingest::VerifyOutcome::hash_backfill.
This mutates only the engine-owned sidecar
(crate::anchor::ANCHOR_SIDECAR_PATH): no entity content, no
section, no _hash is touched — an anchor-only commit yields zero
entity deltas by construction. Guards enforced at this write seam,
not left to callers:
- only a hash-bearing class (
anchored/derived) may gain a hash — anauthored/informed-byanchor is never written, whatever the caller observed; - an anchor that already carries a hash is never overwritten — the recorded hash is the drift baseline, so the backfill is idempotent (a second identical call stages nothing and produces no commit).
Returns how many anchors gained a hash. Zero writes ⇒ no commit.
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 deployment’s declaration when one exists (see
Self::declare_mem_origin), else the mount’s already-decided
writable/read-only posture (fixed at adopt/mount time) — it never
scans content, and both levers are consumer-side 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 declare_mem_origin(
&mut self,
mem: impl Into<String>,
origin: OriginClass,
)
pub fn declare_mem_origin( &mut self, mem: impl Into<String>, origin: OriginClass, )
Declare a mem’s data-trust origin as a deployment fact — the embedding process (a curated hosted read tier, an app that vouches for a bundled mem) overrides the writability inference for one mem. Composition-layer-only by design: not persisted, not reachable over MCP, never derived from mem content — the operator running the process is the only authority that can set it, so a served mem the deployment does not vouch for keeps reporting third-party on every surface. (Deliberately absent from UniFFI/CLI: those surfaces operate a workspace, not a deployment; the CLI counterpart would be a workspace-config knob no use case demands yet.)
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) -> &BindingConfigs
pub fn pipeline_configs(&self) -> &BindingConfigs
The pipeline configs — the v2 single-record binding store — loaded from the workspace 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_projection_json edit entry point.
Serialization-boundary callers (UniFFI, where serde does not live)
get the store in one call and deserialize on their side.
Shape: { "bindings": [{ mem, name, config }] } — the v2
single-record store (config carries the whole binding: inline
sources, operations, everything). The mediums / facets /
ingests keys are gone with their record kinds. This reads the
live binding store fresh (like the brief path) rather than the
in-memory snapshot, so an edit shows back immediately. A missing
root or a legacy/unreadable store yields the fallback empty object.
Sourcepub fn set_pipeline_configs(&mut self, configs: BindingConfigs)
pub fn set_pipeline_configs(&mut self, configs: BindingConfigs)
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 entity_anchors(&self, id: &EntityId) -> Vec<Anchor>
pub fn entity_anchors(&self, id: &EntityId) -> Vec<Anchor>
The stored provenance anchors for id, read from its mem’s
anchors sidecar. Empty for an entity with none, an unknown mem, or
a backend that does not persist anchors (a pre-anchor archive / any
sealed read-only mount). Additive read surface (E3a): the
resolution model lives in crate::anchor
(crate::anchor::resolve_anchor / crate::anchor::compose_entity_anchors);
the live per-anchor state (which requires observing the source
artifacts through the medium/preparation pipeline) is E3b’s concern.
Sourcepub fn entity_anchors_resolved(&self, id: &EntityId) -> Vec<ResolvedAnchor>
pub fn entity_anchors_resolved(&self, id: &EntityId) -> Vec<ResolvedAnchor>
The stored anchors for id, each paired with its live resolution
state when the engine could observe the source artifact this pass.
Additive over Self::entity_anchors: the durable data is unchanged;
state is the crate::anchor::resolve_anchor outcome against an
observation the engine produces here. It is produced only for a
path-namespace, single-medium mem (codebase / filesystem) whose
medium root resolves from the workspace — the engine observes
working-tree existence at the current HEAD:
- artifact absent ⇒
AnchorState::Orphaned; - artifact present, non-hash class (
authored/informed-by) ⇒Resolves; - artifact present, hash-bearing class (
anchored/derived) ⇒ the prepared-content hash comparison decides:Resolveson a match,Driftedon a stable-medium mismatch,Recheckon an unstable medium or when a hash is unavailable on either side (a hash-less anchor, atreegrain, an unreadable artifact).
state is None (unobserved — never a fabricated state) when the mem
has no single path-medium, no workspace root, or the grain/namespace is
not a filesystem path. Non-path mediums / commit-pinned reads stay
deferred (E3b’s remaining leg).
Sourcepub fn anchors_referencing_artifact(
&self,
artifact_path: &str,
) -> Vec<(EntityId, Anchor)>
pub fn anchors_referencing_artifact( &self, artifact_path: &str, ) -> Vec<(EntityId, Anchor)>
Reverse anchor lookup: every (entity_id, anchor) across all mems
whose anchor references artifact_path. This is the query the
rebuilt check-realization hook consumes — given the file an agent
just edited, which entities anchored to it. A span/file/tree
anchor references the path when its base path (locator suffix
@commit / #span stripped) equals the path, or — for a tree
grain — when the path lies under the tree. Path-shaped grains only;
url / entity anchors are matched by exact base equality.
Sourcepub fn mem_anchors_resolved(&self, mem: &str) -> Vec<(EntityId, ResolvedAnchor)>
pub fn mem_anchors_resolved(&self, mem: &str) -> Vec<(EntityId, ResolvedAnchor)>
Every (entity_id, resolved anchor) in mem, read from its anchors
sidecar once and each paired with its live resolution state (the
same observation Self::entity_anchors_resolved produces per entity,
computed here mem-wide in a single sidecar read). Empty for an unknown
mem, a backend that persists no anchors, or a mem with none.
Additive read surface: the durable data is unchanged; state is the
crate::anchor::resolve_anchor outcome against an observation the
engine produces for a single path-namespace medium, or None when
unobserved (never fabricated). The verify pipeline consumes it to
adjudicate a mem’s anchors against the source; audit/health can reuse it.
Sourcepub fn verify_mem_anchors(
&self,
mem: &str,
) -> Result<MemAnchorVerification, EngineError>
pub fn verify_mem_anchors( &self, mem: &str, ) -> Result<MemAnchorVerification, EngineError>
Standalone anchor verification — “do my sources still say what I
recorded?” for one mem, regardless of how it was built. Walks the
mem’s anchor sidecar through the shared per-anchor mechanism
(Self::observe_anchor via Self::mem_anchors_resolved) and
classifies every anchor into the report vocabulary: resolved
(source present, hash matches or non-hash class), drifted
(present, hash differs, stability stable), recheck (hash
differs under unstable, or a hash is missing on either side),
unresolvable (source absent, or a grain/medium the mechanism
does not reach — never fabricated into drift). Read-only on mem
content: pure sidecar read + filesystem observation, no commit on
any backend. A mem with no anchors returns an empty report.
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 derivation_report(
&self,
mem: &str,
) -> Result<Vec<DerivationFinding>, EngineError>
pub fn derivation_report( &self, mem: &str, ) -> Result<Vec<DerivationFinding>, EngineError>
Derivation-staleness report for one mem (agent-trust plan 12):
every EXPLICIT edge whose rel-type the mem’s schema declares
derivation: true, compared against its recorded baseline.
Baseline differs from the target’s current hash → stale;
no baseline recorded (edge predates the declaration, or was
load-derived) → unbaselined, distinctly — never fabricated
as fresh or stale. Fresh edges are not reported. A mem whose
schema declares no derivation rel-types returns the empty
report; an unreadable sidecar reads as empty (every edge
unbaselined) rather than an error.
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 bookkeeping clients 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 quarantined_mems(&self) -> &[QuarantinedMem]
pub fn quarantined_mems(&self) -> &[QuarantinedMem]
The quarantine roster: mems that failed their mem-level boot step and serve nothing until repaired + reloaded. Empty on a fully healthy workspace. Surfaced on overview and health.
Sourcepub fn quarantine_reason(&self, mem: &str) -> Option<&QuarantinedMem>
pub fn quarantine_reason(&self, mem: &str) -> Option<&QuarantinedMem>
The quarantine entry for mem, when it is quarantined.
Sourcepub fn unknown_mem_error(&self, mem: &str) -> EngineError
pub fn unknown_mem_error(&self, mem: &str) -> EngineError
The typed error for a mem name that did not resolve to a
serving mount: MEM_QUARANTINED (carrying the underlying boot
failure and its repair command) when the mem is on the
quarantine roster, UNKNOWN_MEM otherwise. Every lookup site
that fails to find a mem routes here so a quarantined mem is
never misreported as unknown — honest absence, with the reason.
Sourcepub fn boot_diagnosis(&self) -> Option<(&str, &str)>
pub fn boot_diagnosis(&self) -> Option<(&str, &str)>
The workspace-level boot diagnosis a diagnostic-shell engine
carries (None on ordinarily booted engines).
Sourcepub fn diagnostic_shell(reason_code: String, reason_message: String) -> Engine
pub fn diagnostic_shell(reason_code: String, reason_message: String) -> Engine
Build a mem-less diagnostic-shell engine for a workspace whose
boot failed at the WORKSPACE level (nothing loadable — e.g. an
unparseable store). It serves no mems and no entities; its one
job is answering overview/health with the typed boot diagnosis
so a session can always ask WHY the graph is gone — the MCP
server serves this instead of exiting into -32000 Connection closed (degrade, never disappear).
Sourcepub fn extend_quarantine(&mut self, entries: Vec<QuarantinedMem>)
pub fn extend_quarantine(&mut self, entries: Vec<QuarantinedMem>)
Append boot-path quarantine entries recorded outside
from_mounts_inner (backend-instantiation failures happen
before the mount list reaches the engine constructor). Boot
paths only — quarantine is a boot judgment, never a runtime
mutation.
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 orphans(&self) -> Vec<EntityId>
pub fn orphans(&self) -> Vec<EntityId>
Real entities with no incoming or outgoing edges — leaf-declared
types exempt (their edge-less entities are terminal by
construction; see Self::leaf_population).
Sourcepub fn leaf_population(&self) -> BTreeMap<String, usize>
pub fn leaf_population(&self) -> BTreeMap<String, usize>
Count of real entities per leaf-declared type, keyed
<schema_ref>:<type> — the visible population the orphan
exemption covers.
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 constraint_findings(
&self,
mem_filter: Option<&str>,
) -> Vec<ConstraintFindingReport>
pub fn constraint_findings( &self, mem_filter: Option<&str>, ) -> Vec<ConstraintFindingReport>
Standing violations of declared constraints (the health
constraints include) — every non-stub entity whose type
declares constraints its current state violates, in
deterministic (mem, id) order.
Sourcepub fn schema_format_defects(&self) -> Vec<SchemaFormatDefect>
pub fn schema_format_defects(&self) -> Vec<SchemaFormatDefect>
Defective section-format declarations the loaded schemas carry (lenient boot recorded them; install would have refused).
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 status(&self) -> Status
pub fn status(&self) -> Status
Engine-wide crate::ops::Status across every mount — the graph
counts behind memstead status (renamed from stats with the
command, D11; fields unchanged).
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 review_marks(&self) -> Vec<ReviewMarkStatus>
pub fn review_marks(&self) -> Vec<ReviewMarkStatus>
Every mem’s review mark (or its absence) with the current head. Markless mems are ordinary entries, never errors.
Sourcepub fn set_review_mark(
&mut self,
mem_name: &str,
target: Option<&str>,
note: Option<&str>,
) -> Result<SetReviewMarkOutcome, EngineError>
pub fn set_review_mark( &mut self, mem_name: &str, target: Option<&str>, note: Option<&str>, ) -> Result<SetReviewMarkOutcome, EngineError>
Set (or clear, with target: None) a mem’s review mark to an
explicitly named state. The target is validated against the
backend’s cursor vocabulary before anything is written —
git-branch cursors must resolve to a known commit, folder
cursors must parse as the changelog’s RFC3339 timestamp shape —
and an invalid target refuses with INVALID_CURSOR, leaving
the mark untouched. Provenance (note gating, warn-and-commit)
mirrors set_mem_sync_state; the config write commits with
the caller’s note.
Sourcepub fn review_mark_diff(
&self,
mem_name: &str,
rename_similarity: Option<f32>,
) -> Result<ChangesReport, EngineError>
pub fn review_mark_diff( &self, mem_name: &str, rename_similarity: Option<f32>, ) -> Result<ChangesReport, EngineError>
The accumulated per-entity delta from the mem’s review mark to
its current head — exactly the envelopes changes_since
reports for the mark’s cursor. A markless mem refuses with
REVIEW_MARK_NOT_SET (marklessness is known from the roster; a
silent empty answer would equate “no mark” with “no changes”).
Source§impl Engine
impl Engine
Sourcepub fn mem_topology(&self, mem: &str) -> Result<MemTopology, EngineError>
pub fn mem_topology(&self, mem: &str) -> Result<MemTopology, EngineError>
Project mem’s current topology from the live store — every
entity in the mem, every relationship edge sourced in the mem
(cross-mem targets marked), and the mem’s community roster from
the global partition. Recomputed on every call, never
incremental: deleted or renamed entities are simply absent from
the next projection. Unknown mems refuse with
crate::EngineError::UnknownMem.
Source§impl Engine
impl Engine
Sourcepub fn add_projection_json(
&mut self,
mem: &str,
name: &str,
projection_json: &str,
note: Option<&str>,
) -> Result<(), PipelineEditError>
pub fn add_projection_json( &mut self, mem: &str, name: &str, projection_json: &str, note: Option<&str>, ) -> Result<(), PipelineEditError>
Create a binding from a JSON BindingPatch applied to the default
scaffold: the caller may supply any author-editable field, including
the inline sources and a full operations block; an absent block
scaffolds the default build (discovery / loop / batch 20). See
add_binding_json for the refusals (duplicate, missing
destination_mem, in-record validation).
Sourcepub fn update_projection_json(
&mut self,
mem: &str,
name: &str,
projection_json: &str,
note: Option<&str>,
) -> Result<(), PipelineEditError>
pub fn update_projection_json( &mut self, mem: &str, name: &str, projection_json: &str, note: Option<&str>, ) -> Result<(), PipelineEditError>
Patch a binding from a JSON BindingPatch — absent fields are
preserved (tail preservation, extended to every field); explicit
null clears intent / rules / prune; a present sources or
operations block replaces that whole block; version stays
engine-managed. See update_binding_json for the refusals
(not-found, in-record validation).
Sourcepub fn delete_projection(
&mut self,
mem: &str,
name: &str,
note: Option<&str>,
) -> Result<(), PipelineEditError>
pub fn delete_projection( &mut self, mem: &str, name: &str, note: Option<&str>, ) -> Result<(), PipelineEditError>
Delete a binding and refresh the snapshot. See delete_binding.
Sourcepub fn rename_projection(
&mut self,
mem: &str,
old: &str,
new: &str,
note: Option<&str>,
) -> Result<(), PipelineEditError>
pub fn rename_projection( &mut self, mem: &str, old: &str, new: &str, note: Option<&str>, ) -> Result<(), PipelineEditError>
Rename a binding and refresh the snapshot. See rename_binding.
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