pub trait MemBackend: Send + Sync {
Show 16 methods
// Required methods
fn list_entities(&self) -> Result<Vec<PathBuf>, BackendError>;
fn read_entity(
&self,
rel_path: &Path,
) -> Result<Option<Vec<u8>>, BackendError>;
fn write_entity(
&self,
rel_path: &Path,
content: &[u8],
) -> Result<(), BackendError>;
fn delete_entity(&self, rel_path: &Path) -> Result<(), BackendError>;
fn move_entity(&self, from: &Path, to: &Path) -> Result<(), BackendError>;
fn commit(
&self,
message: &str,
ctx: &CommitContext<'_>,
) -> Result<CommitId, BackendError>;
fn append_provenance(&self, record: &Provenance) -> Result<(), BackendError>;
fn read_provenance(
&self,
cursor: Option<&str>,
) -> Result<Vec<Provenance>, BackendError>;
// Provided methods
fn discard_pending(&self) -> Result<(), BackendError> { ... }
fn commit_with_expected_parent(
&self,
message: &str,
ctx: &CommitContext<'_>,
_expected_parent: Option<&str>,
) -> Result<CommitId, BackendError> { ... }
fn current_head(&self) -> Result<Option<String>, BackendError> { ... }
fn read_mem_config(&self) -> Result<Option<Vec<u8>>, BackendError> { ... }
fn read_archive_provenance(&self) -> Result<Option<Vec<u8>>, BackendError> { ... }
fn write_mem_config(&self, _bytes: &[u8]) -> Result<(), BackendError> { ... }
fn write_mem_config_with_note(
&self,
bytes: &[u8],
_note: Option<&str>,
) -> Result<(), BackendError> { ... }
fn delete_artifacts(&self) -> Result<(), BackendError> { ... }
}Expand description
Mem-backend trait. Implementations live next to the backend’s
other code (folder under crate::storage::filesystem; git-branch
in the renamed-from-memstead-git-branch crate; archive under the
archive read-paths in crate::entity once that wiring lands).
Methods are not split into Read / Write sub-traits because
the engine’s mutation paths frequently need both surfaces on the
same backend handle (read current bytes, validate, write new
bytes). Backends that cannot write return BackendError::Sealed
from the write methods — typed and stable so callers branch on
the discriminant rather than parsing a message string.
Required Methods§
Sourcefn list_entities(&self) -> Result<Vec<PathBuf>, BackendError>
fn list_entities(&self) -> Result<Vec<PathBuf>, BackendError>
Mem-relative paths of every entity-bearing file the backend holds. Order is not specified; callers that need stable ordering sort.
Sourcefn read_entity(&self, rel_path: &Path) -> Result<Option<Vec<u8>>, BackendError>
fn read_entity(&self, rel_path: &Path) -> Result<Option<Vec<u8>>, BackendError>
Read raw bytes at rel_path. Ok(None) for a missing path
(idempotent reads); Err for IO or backend-specific failures.
Sourcefn write_entity(
&self,
rel_path: &Path,
content: &[u8],
) -> Result<(), BackendError>
fn write_entity( &self, rel_path: &Path, content: &[u8], ) -> Result<(), BackendError>
Upsert content at rel_path. Pending until Self::commit.
Sourcefn delete_entity(&self, rel_path: &Path) -> Result<(), BackendError>
fn delete_entity(&self, rel_path: &Path) -> Result<(), BackendError>
Remove rel_path. Idempotent: no-op when the path is already
absent. Pending until Self::commit.
Sourcefn move_entity(&self, from: &Path, to: &Path) -> Result<(), BackendError>
fn move_entity(&self, from: &Path, to: &Path) -> Result<(), BackendError>
Rename from to to. Pending until Self::commit. Errors
when to already exists.
Sourcefn commit(
&self,
message: &str,
ctx: &CommitContext<'_>,
) -> Result<CommitId, BackendError>
fn commit( &self, message: &str, ctx: &CommitContext<'_>, ) -> Result<CommitId, BackendError>
Flush pending mutations into a single commit. Returns the
resulting opaque CommitId; backends without history
return a synthetic id (UNIX-nanos + counter, hex) so callers
always get a non-empty cursor.
Sourcefn append_provenance(&self, record: &Provenance) -> Result<(), BackendError>
fn append_provenance(&self, record: &Provenance) -> Result<(), BackendError>
Append a Provenance record to the backend’s mutation log.
Persistence form differs per backend — JSONL line, commit
trailer, etc. — but the in-memory shape is identical.
Sourcefn read_provenance(
&self,
cursor: Option<&str>,
) -> Result<Vec<Provenance>, BackendError>
fn read_provenance( &self, cursor: Option<&str>, ) -> Result<Vec<Provenance>, BackendError>
Read provenance entries since cursor (opaque,
backend-defined: a commit SHA for git-branch, an RFC-3339
timestamp for folder, ignored for archive). None cursor
means “from the beginning”.
Provided Methods§
Sourcefn discard_pending(&self) -> Result<(), BackendError>
fn discard_pending(&self) -> Result<(), BackendError>
Discard every pending (uncommitted) mutation, returning the
staging buffer to empty without producing a commit. The
transactional escape hatch for stage-then-commit callers:
the atomic batch_update stages each item’s write into the
pending set, and when a later item fails validation it calls
this to drop the already-staged writes rather than commit a
half-applied batch. Idempotent — discarding an empty buffer
is a no-op.
Default impl is a no-op: backends that never stage writes
(archive / any sealed backend) have no buffer to clear. The
folder and git-branch backends override to clear their
pending buffer (the git-branch backend also drops the
captured parent snapshot, symmetric with what commit does
on success).
Sourcefn commit_with_expected_parent(
&self,
message: &str,
ctx: &CommitContext<'_>,
_expected_parent: Option<&str>,
) -> Result<CommitId, BackendError>
fn commit_with_expected_parent( &self, message: &str, ctx: &CommitContext<'_>, _expected_parent: Option<&str>, ) -> Result<CommitId, BackendError>
Commit pending mutations with a parent-ref pinning guard.
When expected_parent is Some, the backend MUST refuse the
commit (Err(BackendError::ParentMismatch { ... })) if its
current head no longer matches the supplied ref — a sibling
writer advanced the on-disk state between the snapshot the
caller pinned and now. When expected_parent is None, the
call is equivalent to Self::commit.
Used by atomic multi-file mutations (notably the planned
referrer-rewriting rename) to surface drift mid-operation
rather than between operations. Backends without history
(folder, archive) inherit the default impl: they ignore
expected_parent because there’s no concept of a parent to
pin against — drift detection on those mounts is a no-op
today and stays a no-op here. The git-branch backend
overrides to check the per-mem branch tip and surfaces
the mismatch with a typed error the engine layer can map to
MEM_RELOADED / RENAME_PARTIAL_FAILURE.
Default impl: ignore expected_parent and delegate to
Self::commit. Bisect-safe — existing callers using
Self::commit directly are unaffected.
Sourcefn current_head(&self) -> Result<Option<String>, BackendError>
fn current_head(&self) -> Result<Option<String>, BackendError>
Opaque cursor pointing at the backend’s current state. The
engine compares against a per-mount cached cursor to detect
drift — a sibling writer (another Engine instance, an
out-of-band git pull, etc.) advancing the on-disk state past
what the engine last read. Backends without history (folder,
archive) inherit the default impl returning Ok(None); the
engine treats None as “no drift signal available” and skips
drift detection for that mount. The git-branch backend
overrides to return the per-mem branch tip’s commit SHA hex.
Returning Err is reserved for backend-internal failures
(refdb hiccup, archive read failure, etc.); the engine logs
the error and treats it as a transient None — drift detection
is best-effort and never blocks the read it accompanies.
Sourcefn read_mem_config(&self) -> Result<Option<Vec<u8>>, BackendError>
fn read_mem_config(&self) -> Result<Option<Vec<u8>>, BackendError>
Read the per-mem .memstead/config.json payload, if any.
Returns the raw bytes the backend has for the mem’s
config. The engine parses via
memstead_schema::config::parse_mem_config and stores the
result on the crate::Engine::mem_config_for accessor.
Default impl returns Ok(None) — backends that don’t
surface a config (or haven’t yet implemented this primitive)
inherit and signal “no config available”. The engine
treats None the same as a parse failure: mem_config_for
returns None for the affected mem, and consumers
(memstead_health { include_config: true }) emit empty
writeGuidance + extra blocks for that mem.
Mirrors the pattern of Self::current_head —
backend-internal capability with a sensible no-op default.
Implementations:
- Folder backend reads
<root>/.memstead/config.json. - Archive backend reads
.memstead/config.jsonfrom inside the zip. - Git-branch backend reads
__MEMSTEAD:mems/<mem>/config.json.
Sourcefn read_archive_provenance(&self) -> Result<Option<Vec<u8>>, BackendError>
fn read_archive_provenance(&self) -> Result<Option<Vec<u8>>, BackendError>
Read the optional authoring-provenance payload
(.memstead/provenance.json) the archive carries, if any.
Returns the raw bytes the engine parses into a
memstead_schema::ArchiveProvenance and surfaces via
crate::Engine::archive_provenance_for. Default impl returns
Ok(None) — a backend with no provenance member (a pre-provenance
archive, the folder/git-branch backends until their read paths
lift) inherits and signals “provenance absent”. Mirrors
Self::read_mem_config.
Sourcefn write_mem_config(&self, _bytes: &[u8]) -> Result<(), BackendError>
fn write_mem_config(&self, _bytes: &[u8]) -> Result<(), BackendError>
Write the per-mem .memstead/config.json payload. Symmetric
counterpart to Self::read_mem_config.
Backends that cannot persist a config (today: archive)
inherit the default and return BackendError::Sealed. The
engine’s create / migrate paths branch on the discriminant
before calling.
Implementations:
- Folder backend writes
<root>/.memstead/config.jsonto disk. - Git-branch backend writes
__MEMSTEAD:mems/<mem>/config.json(workspace-level ref) — its own commit, separate from any per-mem-branch mutation. - Archive backend returns
BackendError::Sealed— sealed archives never re-write configs.
Mirrors the symmetry pattern of
Self::read_entity / Self::write_entity: the trait
surface stays balanced so the engine doesn’t branch on
backend type for write paths.
Sourcefn write_mem_config_with_note(
&self,
bytes: &[u8],
_note: Option<&str>,
) -> Result<(), BackendError>
fn write_mem_config_with_note( &self, bytes: &[u8], _note: Option<&str>, ) -> Result<(), BackendError>
Like Self::write_mem_config but records note (an optional
agent/operator-supplied provenance reason) on the resulting
commit body. The default delegates to the note-less form, so
backends without a commit (folder) simply ignore the note; the
git-branch backend overrides this to thread note into the
__MEMSTEAD-ref commit. Lets set_mem_version carry a --note
like the other commit-producing mem-lifecycle operations.
Sourcefn delete_artifacts(&self) -> Result<(), BackendError>
fn delete_artifacts(&self) -> Result<(), BackendError>
Drop every backend-side artifact for this mem — the
symmetric counterpart to the writes performed by
memstead_mem_create (entity-seed commit on the per-mem
branch + Self::write_mem_config on __MEMSTEAD). Called by
memstead_mem_delete orchestration when delete_files=true and
the delete rule matched, to give the backend a chance to
prune ref-store state the engine alone has the git authority
to touch.
Idempotent: safe to call on a backend whose artifacts already
went away (a sibling engine pruned them, the branch was
deleted manually, etc.). The default impl returns Ok(()) —
backends whose on-disk state is fully captured by the mem
directory (folder, archive) inherit the no-op. The
orchestrator handles its remove_dir_all separately at the
outer layer.
Implementations:
- Folder backend keeps the default — its disk state is the mem directory, which the orchestrator rmdirs.
- Archive backend keeps the default — sealed archives have nothing additional to prune.
- Git-branch backend deletes
refs/heads/<branch_leaf>and commits a tree edit onrefs/heads/__MEMSTEADthat removesmems/<branch_leaf>/config.json.<branch_leaf>is the mem’s full hierarchical path (e.g.planning/plan-q4-revampor the bare<name>for flat layouts).
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".