pub enum EngineError {
Show 67 variants
DuplicateMem(String),
UnknownMem(String),
MemQuarantined {
mem: String,
reason_code: String,
reason_message: String,
},
ReadOnlyMount(String),
CheckNotRecorded {
reason: String,
},
UnknownType {
name: String,
schema_ref: String,
declared: Vec<String>,
suggestion: Option<String>,
},
InvalidTitle(SlugError),
AlreadyExists {
id: String,
existing_title: String,
existing_is_stub: bool,
},
ConstraintUnsatisfied {
entity_type: String,
entity_id: String,
violations: Vec<UnsatisfiedConstraint>,
},
SectionFormatRefused {
entity_type: String,
entity_id: String,
violation: SectionFormatViolation,
},
RequiredOutgoingUnsatisfied {
entity_type: String,
entity_id: String,
missing: Vec<MissingRequiredOutgoingBlock>,
},
NotFound {
id: String,
},
HashMismatch {
id: String,
current: String,
is_stub: bool,
},
HasIncomingRefs {
id: String,
referrers: Vec<ReferrerInfo>,
},
MemHasIncomingRefs {
mem: String,
referrers: Vec<ReferrerInfo>,
},
CrossMemLinkNotAllowed {
from_mem: String,
to_mem: String,
},
CrossMemTargetNotFound {
target_id: String,
target_mem: String,
},
CrossMemEdgeNotDeclared {
source_schema: String,
target_schema: String,
rel_type: String,
from_id: String,
to_id: String,
},
RepairNotNeeded {
id: String,
recovery: String,
},
RenameNoOp {
id: String,
new_title: String,
},
EmptyUpdate {
id: String,
},
RenameBlockedByCrossMemPolicy {
from_mem: String,
blocked_referrers: Vec<BlockedReferrer>,
},
WikiLinkWithoutRelation {
from_id: String,
missing: Vec<MissingWikiLink>,
},
RelationHasBodyLinks {
from_id: String,
to_id: String,
rel_type: String,
body_links: Vec<String>,
},
RenamePartialFailure {
committed_mems: Vec<String>,
failed_mem: String,
failure_cause: String,
},
StubCannotRelate {
id: String,
},
StubNotUpdatable {
id: String,
},
StubNotRenamable {
id: String,
},
InvalidEntityId {
id: String,
reason: String,
},
InvalidWikiLinkTarget {
raw: String,
suggested: Option<String>,
section: String,
link_source: String,
reason: String,
},
InvalidWikiLinkMem {
raw: String,
section: String,
reason: String,
},
ConflictingSectionModes {
section: String,
modes: Vec<String>,
},
RelationshipCycle {
rel_type: String,
from: EntityId,
to: EntityId,
existing_path: Vec<EntityId>,
path_truncated: bool,
},
SetAndUnsetConflict {
keys: Vec<String>,
},
RequiredFieldUnset {
field: String,
entity_type: String,
field_description: Option<String>,
enum_values: Vec<String>,
type_write_rules: Vec<String>,
on_create: bool,
missing: Vec<MissingRequiredField>,
},
MissingRequiredSection {
entity_type: String,
missing_count: usize,
sections: Vec<MissingRequiredSection>,
type_guidance: BTreeMap<String, Vec<String>>,
},
PatchSectionEmpty {
section: String,
},
PatchOldNotFound {
section: String,
current_content: String,
truncated: bool,
},
Validation(ValidationError),
ParseAfterWrite(String),
Parse(ParseError),
Backend(BackendError),
SchemaNotFound {
mem: String,
pin: String,
sources: Vec<SchemaSourceDiagnostic>,
install_hint: Option<String>,
},
EmbeddedSchemaInvalid {
mem: String,
pin: String,
reason: String,
},
SchemaPackageInvalid {
name: String,
version: String,
message: String,
},
SchemaResolverInit(String),
Mem(String),
MemNameCollision {
name: String,
source_origin: String,
},
InvalidInput(String),
UnknownRemote(String),
LocalDivergence {
mem: String,
remote_ref: String,
},
NonFastForward {
mem: String,
remote: String,
},
LocalInvalidState {
mem: String,
remote: String,
detail: String,
},
SchemaViolationInFetch {
mem: String,
ref_name: String,
violations: Vec<String>,
},
PushedCommitsProtected {
mem: String,
target_sha: String,
pushed_shas: Vec<String>,
},
BranchResetHeadMoved {
mem: String,
expected: String,
current: String,
},
UnknownRef(String),
RenameSimilarityOutOfRange {
requested: f32,
allowed_min: f32,
allowed_max: f32,
},
InvalidChangesCursor {
mem: String,
since: String,
},
ReviewMarkNotSet {
mem: String,
},
MemConfigIncomplete {
mem: String,
missing_fields: Vec<String>,
},
MissingRequiredDescription {
rel_type: String,
from_id: String,
to_id: String,
},
DescriptionNotPermitted {
rel_type: String,
from_id: String,
to_id: String,
},
RelationManualAuthoringForbidden {
rel_type: String,
from_id: String,
to_id: String,
guidance: String,
},
SearchUnavailable,
MarkdownExportUnsupportedBackend {
mem: String,
active_backend: String,
supported_backends: Vec<String>,
},
InvalidAnchor(AnchorValidationError),
}Expand description
Errors surfaced by [Engine].
Backend lifts BackendError verbatim through a #[from]
conversion so the engine layer’s error envelope preserves the
backend’s typed Sealed / HashMismatch payloads. The MCP layer
branches on the discriminant when mapping into the typed code
field of its error envelope.
Variants§
DuplicateMem(String)
Engine::from_mounts received two mounts naming the same
mem. Configuration error: the persistence adapter or
caller produced a malformed mount list.
UnknownMem(String)
No mount in this engine names the requested mem. Surfaced before reaching any backend so callers can distinguish “wrong mem name” from “backend failure”.
MemQuarantined
The mem exists in the workspace but failed its mem-level boot
step and is quarantined — it serves nothing until repaired
(degrade, never disappear; quarantine is not tolerance).
reason_message is the underlying typed failure verbatim, its
final clause the repair command; after the repair,
memstead_reload re-attaches the mem without a restart.
ReadOnlyMount(String)
Mutation rejected because the mount declares
[MountCapability::ReadOnly]. Surfaced before reaching the
backend so the typed Sealed payload from the archive
backend never triggers — capability gating runs first.
CheckNotRecorded
A check could not be persisted — either the engine has no workspace root (in-memory engines have no durable check store) or the ledger append failed. A check the caller believes recorded but was not is worse than a refusal, so recording is never best-effort.
UnknownType
Entity type is not declared in the pinned schema for this
mem. Carries the declared types (sorted) and a fuzzy
suggestion so the agent can recover without re-reading the
schema. schema_ref is the pinned <name>@<version>.
InvalidTitle(SlugError)
Title slug is empty / invalid.
AlreadyExists
Create attempted against an id already present in the store.
Names the occupant’s title — distinct titles can derive the
same slug, so the id alone does not tell the caller which
entity holds it. existing_is_stub marks a stub occupant
(reachable via rename; the create path adopts stubs instead
of refusing).
ConstraintUnsatisfied
Write refused: the entity as written would violate a
block-tier declared constraint of its type (severity: block
in the schema’s constraints). Warn-tier violations warn
instead (WarningHint::ConstraintUnsatisfied) — same
evaluation, tier decided by the declaration. violations
restates each violated declaration so the caller can repair
without re-fetching the schema.
SectionFormatRefused
Write refused: a section body violates its schema-declared
markdown format (content / item_pattern / table on the
section, format_severity: block). The code and recovery
payload come from the violation itself
(SECTION_CONTENT_MISMATCH / SECTION_ITEM_PATTERN_MISMATCH
/ INVALID_TABLE_COLUMNS, or SECTION_CONTENT_INVALID for a
reserved setext heading); the payload echoes the declared
example where one exists — for an agent, a conforming
example outperforms any grammar string.
RequiredOutgoingUnsatisfied
Write refused: the entity’s final edge set leaves a
block-tier required_outgoing block unsatisfied
(severity: block on the block). The default warn tier keeps
the long-standing warning behavior; this refusal exists only
where a schema explicitly promoted the block. Shares the
MISSING_REQUIRED_OUTGOING code and missing payload shape
with the warning — one condition, one vocabulary, tier decided
by the declaration.
NotFound
Mutation rejected because the named entity is not in the
store. Distinct from UnknownMem: the mem exists, the
entity does not.
HashMismatch
Optimistic-locking failure: the caller’s expected_hash does
not match the entity’s current content_hash in the store.
current is the live hash — pass it as expected_hash after
re-reading to retry. is_stub is set when the entity is a
stub (no body, no content_hash); the corrective action is to
pass expected_hash: "" rather than re-read via memstead_entity.
Surfaces on details.is_stub so MCP callers branch on the
structured payload instead of parsing the message text — pre-fix
the wire emitted (current: ) with an empty paren that
misdirected toward hash-recovery for a stub-shaped entity.
HasIncomingRefs
Refusal to delete or rename an entity because other entities
in Write-Mems still reference it. There is no force flag
or escape hatch — the agent removes the offending references
(via memstead_relate --remove or memstead_update) before retrying.
referrers carries the typed referrer info (source id,
rel-type, source mem) so the response payload describes the
full surface in one round-trip. ReadOnly-mount referrers are
excluded from this list — they are handled by the residual-
stub demotion path on the destructive mutation.
MemHasIncomingRefs
Refusal to delete a mem because entities in other Write-Mems
still reference entities inside it. Mirrors entity-level
Self::HasIncomingRefs at the mem granularity — the
edge-graph axis (F15 / CLI F8). Revoking a workspace-level grant only closes
the policy axis; this check closes the actual-edge axis so a
mem delete that would orphan cross-mem edges refuses with
the typed envelope listing every offending (from_id, rel_type, source_mem) triple. No force flag — the operator must
memstead_relate --remove (or memstead_update to drop the section)
on each referrer first, then retry. ReadOnly-mount referrers
stay out of this list and route through the residual-stub
demotion path on the destructive mutation, same posture as the
entity-level variant.
CrossMemLinkNotAllowed
Relate across mems rejected because the workspace’s
[cross_mem_links] policy (or the per-create-rule
default_cross_links synthesis) does not permit from_mem → to_mem. Agents adjust the policy or pick a same-mem
target. The hint points at the workspace [cross_mem_links]
section.
CrossMemTargetNotFound
Any add-shaped cross-mem edge write (memstead_relate,
memstead_create.relations[], memstead_update.declare_relations,
or a body wiki-link) to a target whose mem is mounted
MountCapability::ReadOnly and the target is absent. Auto-stub
is unavailable across the engine/ReadOnly-mem boundary (the
engine cannot persist a stub in a mem it has no write access
to), and a read-only mem never gains the entity later — the
target must already exist before the link is written.
CrossMemEdgeNotDeclared
memstead_relate across mems pinning schemas with different
names refused because the source schema’s
cross_mem_relationships: section declares no entry for the
target schema’s domain. Each source schema must explicitly
enumerate outbound cross-mem edges per target domain; the
absence here means the source schema does not speak the target
domain’s vocabulary. Eligibility is name-based — a declaration
covers every version of the named target schema. The agent’s
recovery is to declare the rel-type in the source schema’s
cross_mem_relationships: section under the target’s bare
schema name (to_schema: <name>).
Orthogonal to the cross_mem_links permission policy:
vocabulary and permission fire independently. A policy-admissible
edge that violates vocabulary surfaces here; a vocabulary-admissible
edge that violates policy surfaces as
Self::CrossMemLinkNotAllowed.
RepairNotNeeded
memstead_update received repair-shaped input (relations_unset)
for an entity that currently passes the conformance check.
Repair-powers gate on evidence — a conformance failure on the
target entity — and a conformant entity has the focused tools
instead: memstead_relate(remove) detaches an edge, the additive
memstead_update params evolve content. The entity is not
modified.
RenameNoOp
Rename where the new title would slugify to the existing id. Surfaced as a typed no-op so callers don’t loop on a degenerate retry.
EmptyUpdate
memstead_update / memstead_batch_update payload parsed cleanly but
carries no recognised mutation content — every mutation map is
empty and no relations are declared. Distinct from
UPDATE_NOOP (a warning that fires when mutation content was
provided but matched the current state): EMPTY_UPDATE is
keyed on “no mutation content provided at all”, and refuses
before any mutation work runs so a misspelled/omitted mutation
key doesn’t silently land as succeeded: 1, commit_sha: "".
RenameBlockedByCrossMemPolicy
memstead_rename cannot proceed because one or more cross-mem
referrers would emit a propagated rewrite whose direction the
workspace’s cross_mem_links policy does not permit. The
engine refuses the rename up-front (before any write); the
agent’s recovery is either to grant the missing direction in
[cross_mem_links] or to drop the offending edges first.
Each blocked_referrers entry names a single blocked direction
(from_mem → to_mem) — the referrer’s mem and the
renaming entity’s mem, respectively — together with the
count of distinct referrers in that mem that would emit the
blocked rewrite. The direction is the edge’s actual direction
post-rewrite (referrer → renamed), which is what the policy
gates.
WikiLinkWithoutRelation
memstead_create / memstead_update / memstead_batch_update refused
because the post-mutation entity’s section bodies contain
inline wiki-links to targets that have no corresponding
explicit relation in entity.relationships. Strict
wiki-link / relation invariant: every body wiki-link must
have a backing relation. The agent’s recovery is
memstead_relate <this-entity> REFERENCES <target> (or a more
specific rel-type) for each missing entry, then re-issue
the mutation. missing enumerates each violation as a
(section_key, target_id) pair so the agent can fix every
surviving link in one pass. This validator is gated behind
the workspace’s reference-coherence migration completion
marker; workspaces that haven’t been migrated continue
running the permissive auto-stub regime.
RelationHasBodyLinks
memstead_relate --remove refused because the source entity’s
section bodies still contain [[<target>]] (or
[[<mem>:<target>]]) wiki-links pointing at the relation’s
target. Removing the explicit relation while body links
survive would violate the strict wiki-link/relation invariant
(inline links require a backing relation). The agent’s
recovery is memstead_update <source-id> with section content
that drops the wiki-link tokens, then re-issue memstead_relate --remove. body_links enumerates the surviving section keys
so the agent can patch them in one pass.
RenamePartialFailure
A multi-mem memstead_rename partially landed: at least one
mem committed successfully, then a subsequent per-mem
commit aborted (typically because a sibling writer advanced
the failed mem’s head between the rename’s snapshot and the
commit attempt — the parent-ref pin tripped via
BackendError::ParentMismatch). The committed mems’ state
has already landed and is durable; the failed mem’s writes
did not land. The agent’s recovery options: retry the rename
(reload the workspace first so the engine re-derives the right
referrer set), or accept the partial state and reconcile
manually via subsequent mutations.
StubCannotRelate
memstead_relate source is a stub — stubs have no entity_type
and cannot author edges. The agent must promote the stub to a
real entity via memstead_create (stub adoption preserves any
incoming references) before relating. Pre-fix surfaced as the
cryptic UnknownType { name: "" }.
StubNotUpdatable
memstead_update target is a stub — stubs have no body, no
metadata, no schema-resolved type to validate against. The
agent must promote the stub to a real entity via memstead_create
(stub adoption preserves any incoming references) before
updating. Pre-Item-02 surfaced as the cryptic
UnknownType { name: "" } cascade — identical symptom to the
one StubCannotRelate was added to replace on memstead_relate.
StubNotRenamable
memstead_rename target is a stub — stubs do not have a title to
rename (their title is derived from the id). Same recovery
path as Self::StubNotUpdatable.
InvalidEntityId
An EntityId reaching a write path (notably memstead_relate to=)
does not match the wiki-link grammar
(^[a-z0-9-]+(/[a-z0-9-]+)*$ for the slug; ^[a-z0-9-]+$ for
the mem). The gate prevents an auto-stub being created at a
malformed id — once present, that stub would fail any
downstream wiki-link parse that referenced it.
InvalidWikiLinkTarget
A body wiki-link target in a section body failed the strict
slug-form grammar gate. The invariant is that every wiki-link target reaching
entity.relationships carries a grammar-valid EntityId — the
alias-synthesis pass would otherwise emit a relation pointing
at a literal id (e.g. mem--Knowledge Graph) that no
downstream wiki-link parse could ever resolve. raw is the
input between brackets (after alias / .md strip); suggested
is the title_to_slug-derived slug-form the agent lifts
directly into the retry (omitted when the input has no
meaningful canonical form — empty, all-punctuation, all-emoji);
section is the section key whose body carried the link;
source is a stable discriminator ("body_link") future-
proofed against additional ingress surfaces.
InvalidWikiLinkMem
A body wiki-link’s Tier-2 mem prefix [[mem:slug]] failed
the mem-name grammar (^[a-z0-9-]+(/[a-z0-9-]+)*$). Distinct
from InvalidWikiLinkTarget because the recovery is different
— mem names are fixed identifiers in the workspace, not
free-form text the agent can mechanically slugify; the agent
correlates the bad prefix against the workspace’s known mems
rather than reaching for title_to_slug.
ConflictingSectionModes
memstead_update was asked to apply more than one section-
mutation mode (sections, append_sections,
patch_sections) to the same key. The request is ambiguous
and rejected before any disk write. modes lists the
conflicting modes for the key in canonical order.
RelationshipCycle
Adding the proposed edge would close a cycle in an
acyclic-declared subgraph. Carries the existing back-path
[from, …, current, target's intermediates, … from] so MCP
envelopes ship the cycle’s shape without a follow-up
memstead_search. Truncated at
[RELATIONSHIP_CYCLE_PATH_CAP] entries.
Fields
SetAndUnsetConflict
memstead_update received the same metadata key in both metadata
(set) and metadata_unset lists. The request is ambiguous and
rejected before any disk write — the caller picks which map the
key belongs in. keys lists every overlapping key in alphabetical
order so a single envelope describes the full conflict.
RequiredFieldUnset
metadata_unset targeted a required field. Carries the
recovery payload so the agent reads the field’s purpose,
allowed values, and type-level write rules from the envelope
rather than re-fetching the schema.
Also fires from memstead_create when the
caller did not supply a required metadata field that the
schema does not auto-fill (default_value / init_timestamp
/ auto_timestamp all absent). Pre-fix the create path
surfaced this as a MISSING_REQUIRED_FIELD warning and let
the entity land with a placeholder — silently corrupted the
export-then-install round-trip when the placeholder was
invalid for the install-time strict validator. The refusal
fires once per call on the first missing field (declaration
order); subsequent fields surface on the next attempt.
Fields
enum_values: Vec<String>Allowed enum values when the unset field is enum-typed; empty when the field is free-form.
on_create: boolPath discriminator: true when the
create path constructed the variant (caller didn’t supply
the field), false when the update path constructed it
(caller passed metadata_unset: ["field"] against a
required field). The typed code stays REQUIRED_FIELD_UNSET
on both paths; only the rendered prose differs.
Not exposed on the details payload — agents already
branch on the typed code; the new field is for the prose
dispatch only.
missing: Vec<MissingRequiredField>Multi-field accumulator on the create path. Every required-no-default field that was unset, in schema declaration order. Empty on the unset path (where the agent targets one field by definition and the singular fields above are authoritative); always non-empty (and at least a singleton echo of the singular fields) on the create path.
Surfaces on details.missing[] so an agent fixes every
missing field in one round-trip. details.field and
details.missing[0].field agree on the first-missing
entry, keeping the back-compat singular-field shape.
MissingRequiredSection
memstead_create: one or more required sections for the entity’s
type were absent or whitespace-only in the request. Pre-fix
the create path surfaced this as MISSING_REQUIRED_SECTION
warnings and wrote the entity with empty placeholders for
the missing sections; the resulting on-disk state failed the
install-time strict validator, breaking the export-then-
install round-trip. The refusal carries every missing section
(one entry per affected key) plus the type-level type_guidance
map so the agent has a single round-trip recovery via re-call
with the missing content filled in.
Loader / health / memstead_update paths keep their permissive
posture — a legacy on-disk entity created when this gate was
a warning continues to load, surface in health, and accept
partial updates. The refusal is a write-boundary gate, not a
global invariant.
Fields
sections: Vec<MissingRequiredSection>One entry per missing required section, in schema
declaration order. Each entry mirrors the shape of the
pre-fix WarningHint::MissingRequiredSection warning so
agents reading the recovery payload don’t branch on
surface (refusal vs warning).
PatchSectionEmpty
patch_sections targeted a key whose section body is
absent from the entity (or has never been authored).
PatchOldNotFound
patch_sections provided an old substring that does not
appear in the section’s current body. Carries a truncated
snapshot of the current content so the caller can surface
the actual state to the operator.
Validation(ValidationError)
Schema-strictness rejection from the runtime validator
(UNKNOWN_SECTION, UNKNOWN_METADATA, INVALID_ENUM_VALUE).
ParseAfterWrite(String)
Re-parse of the freshly-generated markdown failed. Should
never happen — the generator’s contract is that its output
round-trips through parse_markdown. Surfaces if a future
generator change breaks that invariant.
Parse(ParseError)
A wrapped parse error for completeness; today only the parse-after-write variant above is constructed in the create path.
Backend(BackendError)
A backend operation failed. Inner error carries the typed
payload (e.g. Sealed, HashMismatch, Io).
SchemaNotFound
A mem’s schema pin did not resolve. sources carries the
fixed-order resolution diagnostics (local storage / built-in /
remote) so the caller can tell where the pin failed and spot a
right-name/wrong-version partial match; it surfaces under
details.sources. Empty sources marks an internal lookup miss
(an already-resolved schema absent from the engine’s per-mem
map), not a genuine source-resolution failure.
The MESSAGE summarises the trail — which sources were searched
and whether the name was found at other versions — so the
distinction between a wrong-version pin and a never-installed
package reaches consumers that never open details (a reported
autonomous loop burned five rounds on the payload-only shape).
install_hint (set by EngineError::with_schema_install_probe
where a workspace root is known) names the authoring package
that exists in the working tree but was never installed, and
the message then points at memstead schema install.
Fields
sources: Vec<SchemaSourceDiagnostic>install_hint: Option<String>Path to an authoring package in the working tree whose
manifest name matches the pin’s name while NO source holds
any version of that name — i.e. the package was authored
but never installed. None when no such package exists,
when the name is installed at other versions (a version
mismatch is a different fix), or when no workspace root
was available to probe.
EmbeddedSchemaInvalid
A sealed schema package carried inside a mem archive could not
be loaded — the archive’s own .memstead/schema/ tree is
broken. Deliberately NOT SchemaNotFound: the package is right
here, so the recovery is never “obtain the schema and install
it”. The message quotes the loader’s own diagnosis and the
refusal leaves nothing mounted and nothing staged; only the
publisher can fix it.
SchemaPackageInvalid
A schema package handed to install_schema failed validation —
the loader’s semantic checks or the section-heading round-trip
gate. The engine refuses to seal an invalid schema onto
__MEMSTEAD: install time is the last moment the author can
act, because a schema already sealed keeps loading even when a
later rule would refuse it.
SchemaResolverInit(String)
memstead_schema::builtins::load_builtin_schemas itself failed.
Surfaces during Engine::from_mounts; should never trip in
practice (the built-in catalogue is statically embedded), but
the failure path is preserved so a future on-disk catalogue
switch lifts cleanly.
Mem(String)
Generic mem-level error message — used by accessors that
surface “mem exists, but the requested resource is not
available for this backend” (e.g. gitdir_for against a
folder mount, worktree_for against a git-branch mount).
MemNameCollision
register_writable_mem rejected because name is already
registered (writable OR read-only). source_origin is the
human-readable description of the colliding registration,
rendered via [MemOrigin::render_source] for writable
entries or a stand-in for read-only ones.
InvalidInput(String)
Lifecycle orchestrator rejected the input. Carries a single free-form message — the orchestrator’s typed payload (note length, malformed path, etc.) is the message text.
UnknownRemote(String)
memstead_fetch / memstead_pull / memstead_push named a remote that is
not configured on the workspace’s mem-repo. Typed code
UNKNOWN_REMOTE. Recovery: configure the remote via
memstead mem-repo remote-add <name> <url>.
LocalDivergence
memstead_pull refused because the local branch has diverged from
the remote-tracking ref — fast-forward is impossible without
losing local commits. Recovery: run memstead branch-reset to the
remote-tracking ref (if the local commits are dispensable) or
run a replay workflow to rewrite them onto the new remote tip.
Typed code LOCAL_DIVERGENCE.
NonFastForward
memstead_push refused because the push would not be a fast-forward
against the remote and the caller did not pass force: true.
Typed code NON_FAST_FORWARD. Recovery: re-fetch + replay, or
re-issue with force: true (warning: rewrites the remote’s
view of the branch — other peers will see the rewrite).
LocalInvalidState
memstead_push refused because the local state failed pre-push
schema validation. The remote was not contacted. Recovery: fix
the schema violations (use memstead_health to find them) and
retry. Typed code LOCAL_INVALID_STATE.
SchemaViolationInFetch
memstead_pull (or any future merge path that consumes fetched
commits) refused because the prospective post-merge tree
contains entities that fail schema validation. The branch
pointer was not moved. violations carries one entry per
offending entity — typically (relative_path, parse_error)
pairs rendered as strings — so the caller can surface the
remediation surface without re-walking the tree. Typed code
SCHEMA_VIOLATION_IN_FETCH.
PushedCommitsProtected
memstead_branch_reset refused because at least one commit that
would be discarded by the reset is already reachable from a
refs/remotes/* ref (the engine’s definition of “pushed”).
pushed_shas lists the offending commits. The agent’s
recovery is to pick a target SHA that does not strand a pushed
commit, or to push the pre-reset state under a different
branch name first. Typed code: PUSHED_COMMITS_PROTECTED.
BranchResetHeadMoved
branch_reset refused because the live branch head no longer
matches the head the caller observed (expected_head) — a
sibling writer advanced the mem, and resetting now would discard
that foreign work. Optimistic concurrency for history rewrites;
the caller re-reads and re-decides. Typed code:
BRANCH_RESET_HEAD_MOVED.
UnknownRef(String)
memstead_diff (or any future ref-comparing op) received a ref
that does not resolve against the workspace’s mem-repo.
Carries the ref string verbatim so the caller can fix the
input. Typed code UNKNOWN_REF.
RenameSimilarityOutOfRange
memstead_changes_since received a rename_similarity value
outside the allowed range. Maps to wire code INVALID_INPUT
with details.allowed_range: [min, max] and
details.requested. Promoted from the prior silent-clamp + LIMIT_CLAMPED warning so
nonsense inputs surface as recoverable refusal rather than
silent rounding.
InvalidChangesCursor
memstead_changes_since / memstead changes --since was given a since
commit cursor the mem’s git repository can’t resolve — a
malformed prefix or a well-formed-but-absent 40-hex. Surfaces the
INVALID_CURSOR code (the documented contract for this op, which
the CLI previously leaked as the MEM_ERROR catch-all) so a
sync loop branches cleanly: INVALID_CURSOR → re-seed from the
empty-tree sentinel; MEM_ERROR → genuine backend fault.
details.since carries the offending cursor untruncated.
ReviewMarkNotSet
review_mark_diff was called on a mem with no review mark set.
Marklessness is a first-class, known-from-the-roster state — the
diff surface refuses typed rather than silently equating “no
mark” with “no changes”.
MemConfigIncomplete
Mem config is missing a required field that the engine
itself would normally populate (today: version at mem
init). Surfaced on the export path — pre-fix this collapsed
to INTERNAL with a misleading .memstead/config.json reference
that doesn’t match the mem-repo backend’s blob layout.
Recovery: run memstead mem set-version <mem> <version> to
populate the field, then retry the export. F1.
MissingRequiredDescription
memstead_relate (or a declare_relations entry) targeted a
rel-type whose schema declares per_edge_description: required without supplying a description. Recovery: re-issue
the call with --description "<text>" describing why this
particular edge exists (the rel-type’s name documents the
kind of edge; the description documents the instance).
DescriptionNotPermitted
memstead_relate (or a declare_relations entry) supplied a
description for a rel-type whose schema declares
per_edge_description: forbidden. Recovery: drop the
description parameter — the rel-type’s name describes the
edge; per-edge text is not permitted on this rel-type.
RelationManualAuthoringForbidden
memstead_relate (or a declare_relations / memstead_create’s
inline relations: entry) targeted a rel-type whose schema
declares manual_authoring: forbidden. The rel-type is
reserved for engine-emitted synthesis (the body-link →
relation alias machinery, typically). Recovery: don’t author
the relation explicitly; instead author a body wiki-link
[[target]] in the source’s section content, which the
engine surfaces as the appropriate alias relation
automatically.
Full-text search is unavailable in the current engine build —
Engine::search is callable on every target so JS / FFI
consumers don’t need to re-shape their call sites, but wasm32
builds omit the tantivy index entirely (its native-only
transitives — getrandom 0.2 without js, memmap2, rayon,
zstd-sys — block WASM compilation). Browser consumers route
queries to the bridge’s memstead_search endpoint. The MCP layer
maps this to typed code SEARCH_UNAVAILABLE_IN_WASM.
MarkdownExportUnsupportedBackend
memstead export --format markdown --mem-name <V> was called
against a mem whose active backend doesn’t support markdown
regeneration in place (today: every backend other than
folder). Pre-fix this collapsed to a silent
ExportResult { written: 0, unchanged: 0 } masquerading as
success. Recovery: use --format mem to produce a portable
.mem archive, which every backend supports.
InvalidAnchor(AnchorValidationError)
A memstead_create / memstead_update anchors[] element was
malformed — an unknown provenance class or grain, a missing artifact
reference, a content hash on a class without hash semantics, or a
grain the resolving medium’s namespace cannot express. The whole
mutation refuses and the entity is not written; the wrapped
crate::anchor::AnchorValidationError carries the recovery
details (offending field, bad value, allowed set). Typed code
INVALID_ANCHOR.
Implementations§
Source§impl EngineError
impl EngineError
Sourcepub fn code(&self) -> &'static str
pub fn code(&self) -> &'static str
Stable, surface-independent error code token.
Each surface (MCP envelope, CLI envelope, UniFFI binding) maps the variant to its wire shape; the code returned here is the canonical name agents key on. Add a new code here when a new variant lands; do not invent ad-hoc strings inside the per-surface mapping.
Sourcepub fn details(&self) -> Value
pub fn details(&self) -> Value
Variant-specific recovery payload, rendered as a structured
JSON object that surfaces under error.details in MCP /
CLI envelopes.
Pre-fix the
batch-update per-item envelope (batch_error_envelope)
shipped {} for every typed code except Validation, while
the singleton-call surfaces (CliError::from_engine_op,
memstead-mcp‘s engine_err_unified) populated structured
payloads per-variant. Two envelopes, two details paths —
agents’ “fix from details” recovery loop worked
differently in batch vs singleton mode. The centralised
helper here gives both surfaces one source of truth.
Returns an empty object for variants whose recovery payload
is the message text alone (no structured fields beyond
code + message).
Sourcepub fn prose_render(&self) -> String
pub fn prose_render(&self) -> String
Render rich, fully-inlined recovery prose for the agent-visible text channel.
Warnings
already render their structured payload inline via
WarningHint::Display; pre-fix errors with rich payloads
collapsed to Display plus format_inline_list_overflow’s
“+N more — see details.X” pointer pointing at a structured
channel the agent’s MCP client doesn’t surface to the model.
This method gives errors the same prose-rich rendering warnings
have, so result.content[0].text is self-recoverable.
Variants whose Display already inlines every recovery field
(no truncation, no “see details” pointer) inherit the default
trait impl — they just to_string(). Override only the
variants that need richer rendering than Display provides.
The structured details() channel is unchanged; consumers
branching on code continue to receive the typed shape. The
Display impl stays terse for logs, tracing, panic messages,
and other non-agent consumers.
Source§impl EngineError
impl EngineError
Sourcepub fn with_schema_install_probe(self, workspace_root: Option<&Path>) -> Self
pub fn with_schema_install_probe(self, workspace_root: Option<&Path>) -> Self
Attach the schema-install hint to a SchemaNotFound where a
workspace root is known: probe the root’s immediate
subdirectories for an authoring schema package (a directory the
schema loader accepts) whose manifest name matches the pin’s
name, and record its path when NO resolution source holds any
version of that name. Any other error variant — and any
SchemaNotFound where the name IS installed at some version
(a version mismatch is a different fix), where sources is
empty (internal miss, no source search happened), or where no
candidate package exists — passes through unchanged. Read-only:
the probe never writes, installs, or seals anything.
Trait Implementations§
Source§impl Debug for EngineError
impl Debug for EngineError
Source§impl Display for EngineError
impl Display for EngineError
Source§impl Error for EngineError
impl Error for EngineError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()
Source§impl From<AnchorValidationError> for EngineError
impl From<AnchorValidationError> for EngineError
Source§fn from(source: AnchorValidationError) -> Self
fn from(source: AnchorValidationError) -> Self
Source§impl From<BackendError> for EngineError
impl From<BackendError> for EngineError
Source§fn from(source: BackendError) -> Self
fn from(source: BackendError) -> Self
Source§impl From<EngineError> for FromArchiveBytesError
impl From<EngineError> for FromArchiveBytesError
Source§fn from(source: EngineError) -> Self
fn from(source: EngineError) -> Self
Source§impl From<EngineError> for BootError
impl From<EngineError> for BootError
Source§fn from(source: EngineError) -> Self
fn from(source: EngineError) -> Self
Source§impl From<ParseError> for EngineError
impl From<ParseError> for EngineError
Source§fn from(source: ParseError) -> Self
fn from(source: ParseError) -> Self
Source§impl From<SlugError> for EngineError
impl From<SlugError> for EngineError
Source§impl From<ValidationError> for EngineError
impl From<ValidationError> for EngineError
Source§fn from(source: ValidationError) -> Self
fn from(source: ValidationError) -> Self
Auto Trait Implementations§
impl !RefUnwindSafe for EngineError
impl !UnwindSafe for EngineError
impl Freeze for EngineError
impl Send for EngineError
impl Sync for EngineError
impl Unpin for EngineError
impl UnsafeUnpin for EngineError
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
Source§impl<T> DowncastSync for T
impl<T> DowncastSync 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