Skip to main content

MemBackend

Trait MemBackend 

Source
pub trait MemBackend: Send + Sync {
Show 19 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 record_pipeline_edit( &self, _kind: &str, _edits: &[(String, Option<Vec<u8>>)], _note: Option<&str>, _verb: &str, ) -> Result<(), BackendError> { ... } fn read_anchors_sidecar(&self) -> Result<Option<Vec<u8>>, BackendError> { ... } fn write_anchors_sidecar(&self, _bytes: &[u8]) -> 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§

Source

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.

Source

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.

Source

fn write_entity( &self, rel_path: &Path, content: &[u8], ) -> Result<(), BackendError>

Upsert content at rel_path. Pending until Self::commit.

Source

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.

Source

fn move_entity(&self, from: &Path, to: &Path) -> Result<(), BackendError>

Rename from to to. Pending until Self::commit. Errors when to already exists.

Source

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.

Source

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.

Source

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§

Source

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

Source

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.

Source

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; the filesystem backend overrides to return the changelog’s last-line timestamp cursor (folder mems with no changelog yet keep None). Archive and in-memory backends stay on the default.

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.

Source

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.json from inside the zip.
  • Git-branch backend reads __MEMSTEAD:mems/<mem>/config.json.
Source

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.

Source

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

Source

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.

Source

fn record_pipeline_edit( &self, _kind: &str, _edits: &[(String, Option<Vec<u8>>)], _note: Option<&str>, _verb: &str, ) -> Result<(), BackendError>

Record provenance for a pipeline-config edit (mediums / facets / projections / ingests). The canonical pipeline config is a plain JSON file under .memstead/ on the workspace root — it has no commit of its own — so backends with a commit timeline mirror the edit into their provenance record; the commit is the audit trail, the disk file stays the read path.

edits: (config_name, Some(bytes)) upserts the mirrored blob, (config_name, None) removes it (a rename passes both). kind is the primitive’s plural (mediums, facets, projections, ingests); verb names the operation for the commit subject.

The default is a successful no-op: folder and archive backends have no commit timeline, so the note is accepted and dropped — the same posture as Self::write_mem_config_with_note. The git-branch backend overrides this to commit the mirror under __MEMSTEAD:pipeline/<kind>/<mem>/<name>.json with note on the commit body.

Source

fn read_anchors_sidecar(&self) -> Result<Option<Vec<u8>>, BackendError>

Read the engine-owned anchors sidecar (crate::anchor::ANCHOR_SIDECAR_PATH) bytes, if any.

The sidecar lives on the mem branch under the .memstead/ umbrella every external reader already filters, so it never surfaces as an entity. Returns the raw bytes the engine parses via crate::anchor::AnchorSidecar::from_bytes; Ok(None) for a mem that has never written anchors.

Default impl returns Ok(None) — a backend that does not persist anchors (a pre-anchor archive, any read-only mount) inherits and signals “no anchors”. Mirrors Self::read_mem_config. The git-branch and in-memory backends override to read the sidecar from their store (pending-buffer precedence, so a staged sidecar write is visible before its commit).

Source

fn write_anchors_sidecar(&self, _bytes: &[u8]) -> Result<(), BackendError>

Stage a write of the engine-owned anchors sidecar so it rides the same commit as the entity mutation that produced it — the atomicity guarantee anchors depend on (rename’s referrer-rewrite, delete’s anchor removal, and branch_reset’s rewind all move entity + anchor state together).

Pending until the next Self::commit — callers stage the entity write, then the sidecar write, then commit once. Backends that cannot persist anchors (archive / any sealed backend) inherit the default returning BackendError::Sealed; the engine’s write path branches on mount capability before calling. The git-branch and in-memory backends override to buffer the sidecar under crate::anchor::ANCHOR_SIDECAR_PATH in the same pending set the entity write used.

Source

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 on refs/heads/__MEMSTEAD that removes mems/<branch_leaf>/config.json. <branch_leaf> is the mem’s full hierarchical path (e.g. planning/plan-q4-revamp or the bare <name> for flat layouts).

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§