Skip to main content

Store

Struct Store 

Source
pub struct Store { /* private fields */ }

Implementations§

Source§

impl Store

Source

pub fn session_budget( &self, session_id: &str, ) -> Result<SessionBudget, StoreError>

Compute the budget spent by the given session_id across every op currently reachable from any branch head. Returns (spent: 0, op_count: 0) for unknown sessions, with cap populated from policy.session_budgets.

Source

pub fn session_budget_cap( &self, session_id: &str, ) -> Result<Option<u64>, StoreError>

Resolve the budget cap configured for session_id from policy.json’s session_budgets (#292 slice 2). Returns None when no enforcement is configured.

Source

pub fn all_session_budgets(&self) -> Result<Vec<SessionBudget>, StoreError>

Compute per-session budget rollups across every branch. Returns one entry per distinct session that contributed at least one budget-bearing op. Sorted by session_id so the output is deterministic.

Source§

impl Store

Source

pub fn plan( &self, branch: &str, goal: &str, max_cost: Option<u64>, session_id: Option<&str>, ) -> Result<Plan, StoreError>

lex plan (#307). See module docs.

Source§

impl Store

Source

pub fn open(root: impl AsRef<Path>) -> Result<Self, StoreError>

Open or create a store rooted at root.

Source

pub fn with_dep_resolver(self, resolver: Arc<dyn DepResolver>) -> Self

Install the dependency resolver the write-time gate uses to type-check heads that keep external import edges (#930). Builder-style so a caller can write Store::open(p)?.with_dep_resolver(r).

Source

pub fn set_dep_resolver(&mut self, resolver: Arc<dyn DepResolver>)

Set the dependency resolver in place (for a Store already owned, e.g. behind a Mutex in the HTTP State).

Source

pub fn rebuild_stage_index(&self) -> Result<usize, StoreError>

Build (or top up) the reverse index in one pass over every SigId in the store, rather than relying on lookup_lifecycle to discover entries one at a time. Safe to call at any time, including on a partially-built index (e.g. one left behind by an interrupted request that was populating it lazily): already- indexed stage_ids are skipped, so this only does the work that remains. Returns the number of newly-added entries.

Source

pub fn root(&self) -> &Path

Source

pub fn put_blob(&self, content: &str) -> Result<String, StoreError>

Content-address content and persist it under <root>/blobs/<sha>. Returns the sha. Text wrapper over Self::put_blob_bytes; the id is the sha of the UTF-8 bytes, so it is unchanged from before #1007.

Source

pub fn put_blob_bytes(&self, bytes: &[u8]) -> Result<String, StoreError>

Content-address arbitrary bytes (#1007: files beside the op-log may be binary) and persist them under <root>/blobs/<sha>. Returns the lowercase hex SHA-256 — the same value sha256sum prints. Idempotent: re-putting identical content is a no-op. Concurrency-safe — writes to a unique temp file then atomically renames onto the content-addressed path, so parallel writers of the same content can’t corrupt it.

Source

pub fn get_blob(&self, sha: &str) -> Result<String, StoreError>

Read a blob by its sha as text. UnknownBlob if absent; an InvalidData I/O error if the blob is not UTF-8 (use Self::get_blob_bytes for binary content).

Source

pub fn get_blob_bytes(&self, sha: &str) -> Result<Vec<u8>, StoreError>

Read a blob’s exact bytes by its sha. UnknownBlob if absent.

Source

pub fn blob_len(&self, sha: &str) -> Option<u64>

Byte length of a blob without reading it, or None if absent.

Source

pub fn blob_bytes_used(&self) -> Result<u64, StoreError>

Total bytes held in this store’s blob space (#1007): the sum of every stored blob’s length, ignoring in-flight temp files. What a per-store blob quota is measured against. O(number of blobs).

Source

pub fn has_blob(&self, sha: &str) -> bool

Whether a blob with this sha exists.

Source

pub fn set_blob_ref( &self, namespace: &str, key: &str, sha: &str, ) -> Result<(), StoreError>

Bind key to a blob sha within namespace (e.g. namespace "loom/sprint-abc", key "build-node"). Overwrites an existing binding. The namespace may contain /; neither namespace nor key may contain a .. path component.

Source

pub fn get_blob_ref( &self, namespace: &str, key: &str, ) -> Result<String, StoreError>

Resolve namespace/key to a blob sha. UnknownBlobRef if unbound.

Source

pub fn set_committed_lock( &self, head_op: &str, lock_toml: &str, ) -> Result<(), StoreError>

Record the lex.lock committed with the package head head_op — the exact dependency versions and op-log heads that head was built and type-checks against (#930 phase 2b-1: “HEAD + its committed lex.lock always type-checks”). Content-addressed via Self::put_blob and bound under the lock namespace keyed by the head op, so it is idempotent (a re-push converges) and travels with the package through the same object-sync path as stages and intents. Keyed by head op rather than by branch so re-verifying a historical head resolves it against the lock that head actually committed, not whatever the branch points at now.

Source

pub fn committed_lock( &self, head_op: &str, ) -> Result<Option<String>, StoreError>

The lex.lock committed with head_op, or None when the head carries no committed lock — a dependency-free package, or one published before locks were committed (the write-time gate then has no registry/git dependencies to resolve, exactly as today).

Source

pub fn committed_lock_inherited( &self, head_op: &str, ) -> Result<Option<String>, StoreError>

The lock governing head_op: its own committed lock if it has one, otherwise the nearest ancestor’s (#975).

Self::committed_lock is an exact-key lookup, and a lock is only ever committed for a head a client pushes — or, since #977, for a merge op, which commits the union of its parents’ locks (Self::merged_lock_for_parents). A head landed through /v1/patch has none of its own, so the exact lookup returned None, the dependency resolver got no pins, and a non-inlined head was rejected as unknown_identifier "<alias>" even though every dependency was resolvable. A head inherits its ancestors’ pins until a new lock is committed: the correct model for a patch head, which genuinely has no new pins to record.

Breadth-first, so the nearest ancestor wins. That returns one lock whole, which is only right along a single line of history — which is why a merge op does not rely on it and commits its own merged lock.

Source

pub fn merged_lock_for_parents( &self, parents: &[OpId], ) -> Result<Option<String>, StoreError>

The lock a merge op with parents commits as its own (#977): the union of every parent’s governing lock (Self::committed_lock_inherited).

Inheriting one parent’s lock whole is wrong for a merge: if the source branch introduced a dependency, only the source’s lock pins it, and a merge that inherited the destination’s lock failed its gate as unknown_identifier "<alias>". Unioning fixes that.

parents must be in merge order — the first is the branch being merged INTO (dst), later ones are merged in. Note an Operation’s own parents are sorted (so the op id is order-independent) and do not carry that order; the caller supplies it. It only matters for naming the sides of a conflict and for which entry is kept on a tie. When two parents pin the same package at different versions the merge is refused with StoreError::DependencyConflict rather than silently picking one: either choice changes what one side’s code was tested against. When the versions agree, the first parent’s entry is kept (filling in a head_op it lacks from a later parent).

Ok(None) when no parent is governed by any lock (a dependency-free package), and also when a parent’s lock can’t be parsed — the merge then carries no lock of its own and falls back to inheritance exactly as before #977, rather than refusing a merge over an unreadable lock it did not write. Read-only: nothing is written.

Source

pub fn list_blob_refs( &self, namespace: &str, ) -> Result<BTreeMap<String, String>, StoreError>

All key → sha bindings in a namespace (e.g. every artifact in a sprint). Empty map if the namespace has no bindings yet.

Source

pub fn publish(&self, stage: &Stage) -> Result<String, StoreError>

Publish a stage as Draft. Returns the StageId. Idempotent: republishing the same canonical AST returns the same StageId without writing duplicates.

Source

pub fn publish_signed( &self, stage: &Stage, signer: Option<&Keypair>, ) -> Result<String, StoreError>

Like Self::publish but optionally attaches an Ed25519 signature over the StageId (#227). When signer is Some, the persisted metadata gets a signature field that downstream consumers can verify via lex_vcs::verify_stage_id.

Idempotency: if a metadata file already exists the signature is not re-written. This preserves “republishing is a no-op” even across different signers — promoting a signed stage requires a fresh stage hash anyway, so a metadata overwrite would be the wrong primitive.

Source

pub fn activate(&self, stage_id: &str) -> Result<(), StoreError>

Source

pub fn deprecate( &self, stage_id: &str, reason: impl Into<String>, ) -> Result<(), StoreError>

Source

pub fn tombstone(&self, stage_id: &str) -> Result<(), StoreError>

Source

pub fn resolve_sig(&self, sig: &str) -> Result<Option<String>, StoreError>

The current Active StageId for a signature, or None.

Source

pub fn sig_history( &self, sig: &str, ) -> Result<Vec<StageHistoryEntry>, StoreError>

Per-stage history for a SigId, ordered chronologically by the last transition timestamp. Returns one entry per distinct StageId that has ever been published under sig. Ok(vec![]) if the SigId doesn’t exist in the store.

Used by lex blame to render “where does this fn come from”.

Source

pub fn get_ast(&self, stage_id: &str) -> Result<Stage, StoreError>

Source

pub fn get_asts_for_sigs_bulk( &self, pairs: &[(String, String)], ) -> Vec<Result<Stage, StoreError>>

Bulk AST fetch for callers that already know each stage’s signature — a branch head map, for instance, which is keyed by SigId and whose values are the StageIds it points at.

Prefer this over Self::get_asts_bulk whenever the SigId is in hand, because resolving a StageId back to a SigId is not reliable: a StageId hashes the structural signature plus the implementation, deliberately not the name (docs/INVARIANTS.md), so two functions that differ only in name share one StageId while having two distinct SigIds — and two separate ASTs, one under each sig directory. stage_index maps each StageId to a single sig, so get_ast/get_asts_bulk return whichever of those ASTs the index happens to name, i.e. the wrong name half the time (#826). Reading straight from the sig the caller already knows removes the ambiguity — and skips loading the index at all.

Returns results in the same order as pairs, Err for anything that fails to resolve (mirroring get_ast’s error semantics).

Source

pub fn get_asts_bulk( &self, stage_ids: &[String], ) -> Vec<Result<Stage, StoreError>>

Bulk variant of Self::get_ast for callers resolving many stage_ids at once (e.g. pkg_publish_handler’s old_head scan over every live function in a tenant, once per publish request). get_ast in a loop calls lookup_lifecycle once per stage_id, and lookup_lifecycle’s index-hit path reads and re-parses the entire stage_index.jsonl on every single call — fine for one call, but O(index size × N) for N calls in a row, which dominates once the index itself is large (#825’s follow-up: still correct and far better than the pre-index full-tenant-scan-per-call behavior, but the per-call reparse is itself a real, measured cost — 87.6s for 3,664 calls against a ~14k-line index on the alpibrusl tenant).

This loads the index once for the whole batch and keeps it in memory across all stage_ids, only touching disk again to append genuinely new entries (a positive backfill or a negative “not found anywhere” cache, same as the single-call path) — never to re-read what’s already loaded.

Returns results in the same order as stage_ids, Err for anything that fails to resolve (mirroring get_ast’s error semantics per call).

Source

pub fn get_metadata_for_sig( &self, sig_id: &str, stage_id: &str, ) -> Result<Metadata, StoreError>

The metadata for a stage under a named sig.

Self::get_metadata resolves the sig through the stage index, which is a stage-id-only lookup — and a StageId does not encode the name (#826), so two sigs can share one. Resolving by id then hands both declarations whichever record the index happens to point at. Harmless for a hash, not for content: it gave two different declarations the same documentation, duplicating a module header into a package that had it once.

When the caller knows the sig — a head map is keyed by it — this is the honest lookup.

Source

pub fn get_metadata(&self, stage_id: &str) -> Result<Metadata, StoreError>

Source

pub fn get_status(&self, stage_id: &str) -> Result<StageStatus, StoreError>

Source

pub fn list_stages_by_name(&self, name: &str) -> Result<Vec<String>, StoreError>

Source

pub fn list_sigs(&self) -> Result<Vec<String>, StoreError>

Source

pub fn attach_test(&self, sig: &str, test: &Test) -> Result<String, StoreError>

Source

pub fn list_tests(&self, sig: &str) -> Result<Vec<Test>, StoreError>

Source

pub fn attach_spec(&self, sig: &str, spec: &Spec) -> Result<String, StoreError>

Source

pub fn list_specs(&self, sig: &str) -> Result<Vec<Spec>, StoreError>

Source

pub fn save_trace(&self, tree: &TraceTree) -> Result<String, StoreError>

Source

pub fn load_trace(&self, run_id: &str) -> Result<TraceTree, StoreError>

Source

pub fn list_traces(&self) -> Result<Vec<String>, StoreError>

Source

pub fn publish_program( &self, branch: &str, stages: &[Stage], diff: &DiffReport, new_imports: &ImportMap, activate: bool, ) -> Result<PublishOutcome, StoreError>

Apply a published program to a branch as a sequence of typed operations. Returns the ordered list of op_ids + the new head_op. The caller (lex publish CLI, lex serve’s HTTP handler) is responsible for computing the DiffReport against the current branch head — the diff infrastructure lives in lex-vcs::compute_diff (previously lex-cli) to keep this layer from owning diffing logic.

On success: every op in the returned list is durable in the op log and the branch’s head_op points at the last one. On a no-op (no diff): returns empty ops and the existing head_op unchanged.

Source

pub fn publish_program_signed( &self, branch: &str, stages: &[Stage], diff: &DiffReport, new_imports: &ImportMap, activate: bool, signer: Option<&Keypair>, ) -> Result<PublishOutcome, StoreError>

Signed variant of Self::publish_program (#227). Every stage written under this batch gets the same signer; per-stage keys aren’t supported because the agent identity model treats a publish as a single authorial act.

Source

pub fn publish_program_with_intent( &self, branch: &str, stages: &[Stage], diff: &DiffReport, new_imports: &ImportMap, activate: bool, signer: Option<&Keypair>, intent_id: Option<IntentId>, module_prefixes: &BTreeMap<String, String>, ) -> Result<PublishOutcome, StoreError>

Self::publish_program_signed plus an optional intent_id (#131 / #839): when given, every op this publish emits is stamped with it, so the op log records why the change happened — the prompt / model / session an agent was acting under — not only what it was. lex recall --intent <id> and lex op replay read it back. The caller records the lex_vcs::Intent in the lex_vcs::IntentLog beforehand; this only links ops to it. None is the existing (intent-less) behavior, so op ids for intent-less publishes are unchanged.

Source

pub fn unsatisfiable_owner(&self, sig: &str, stage: &str) -> Option<String>

The sig stage is actually filed under, when binding it to sig is provably unsatisfiable (#992): (sig, stage) cannot be read, yet the store holds that very stage under a different sig. None when the pair reads fine, and also when the stage is simply absent — an absent blob (mid-pull, partial clone) proves nothing about satisfiability.

Presence is judged by the stage file existing under the sig (a full snapshot or a delta), not by decoding it: this runs over every head entry on a ref advance, and a stat is all the question needs.

Source

pub fn check_pairs_satisfiable<'a>( &self, pairs: impl IntoIterator<Item = (&'a String, &'a String)>, ) -> Result<(), StoreError>

Refuse the first provably unsatisfiable pair in pairs with StoreError::UnsatisfiablePair — the write-time half of #992’s “always-valid HEAD”. See Self::unsatisfiable_owner for what counts.

Source

pub fn stranded_head_entries( &self, head_pairs: &[(String, String)], ) -> Vec<OperationKind>

Head entries naming a (sig, stage) no store can hold, where that same stage is readable under a different sig at the head (#992).

This is the fingerprint a pre-#992 ChangeEffectSig leaves behind. It bound the old sig to the new stage, while the store files an implementation under the sig its own AST hashes to — and that AST declares the new effects. The head then names one declaration twice and one of the two can never be resolved, so every render of that head fails and any release cut from it is born broken (lex-web@0.4.0).

Fixing the op stops new ones appearing but cannot repair a head that already has one: the publish diff is keyed by declaration name and reads the old side through the ASTs it can resolve, so a stranded entry is invisible to it and no op is ever emitted.

The second condition is the whole reason this is safe to do without asking. Requiring the stage to be resolvable under another sig proves the content is present and merely filed elsewhere, so retiring the entry discards nothing. A store that is simply missing blobs — mid-pull, a partial clone, a GC’d object — fails that test, because there neither sig resolves, and is left strictly alone.

Source

pub fn derive_imports_from_oplog( &self, branch: &str, ) -> Result<ImportMap, StoreError>

Source

pub fn apply_operation_checked( &self, branch: &str, op: Operation, transition: StageTransition, candidate: &[Stage], ) -> Result<OpId, StoreError>

Apply an operation to a branch and advance its head_op.

The single advance path. Validates parents via lex_vcs::apply, persists the operation via the op log, then atomically advances the branch file’s head_op via set_branch_head_op.

Errors:

  • UnknownBranch: branch does not exist (no op is persisted).
  • Apply(ApplyError::StaleParent): the op’s parents don’t match the branch head — head is unchanged. Callers that want retry-on-stale (e.g. lex publish re-running against a moved head) match on this variant explicitly.
  • Apply(ApplyError::UnknownMergeParent): a merge op’s second parent isn’t in the log.
  • Io: filesystem error during persist or branch advance.

Crash recovery: between op persist and branch advance, a crash can leave an orphan op record in the log with no branch pointing at it. The op is content-addressed and cheap to re-derive from the same source. See Apply a single op against branch, gated on the candidate program typechecking. The per-op variant of #130’s write-time gate — counterpart to Self::publish_program’s batch-mode check.

candidate is the sequence of Stages that would exist on this branch after the op is applied. Caller’s responsibility: today neither lex-store nor lex-vcs reconstruct the candidate from the op + branch state on behalf of the caller. The natural callers (HTTP POST /v1/publish for a single op; agent harnesses driving merges via the future #134 API) already have the candidate in memory.

On rejection: branch head unchanged, no op record persisted. Same atomicity guarantee as the publish path.

§Why a separate method, not a flag on apply_operation

apply_operation accepting Option<&[Stage]> and silently skipping the gate on None is exactly the kind of “secretly opt-out” path #130 is trying to remove. The honest split: apply_operation for the one caller that already typechecked its input up front (publish_program), apply_operation_checked for callers holding the candidate, Self::apply_operation_gated for single-parent callers that hold only the transition (/v1/patch), and Self::apply_merge_op_gated for merge commits (#833).

Source

pub fn apply_operation_checked_with_intent( &self, branch: &str, op: Operation, transition: StageTransition, candidate: &[Stage], intent: Option<&Intent>, ) -> Result<OpId, StoreError>

Self::apply_operation_checked that also records intent in the lex_vcs::IntentLog (#837 piece A). The caller stamps the intent’s id on op (Operation::with_intent); this only makes sure the intent record exists, and does so after the type-check and session-budget gates and before the head moves — so a rejected write leaves no op and no intent behind, the same “no footprint” guarantee the gate already gives the op record. None is exactly apply_operation_checked.

Source

pub fn candidate_program_for( &self, branch: &str, transition: &StageTransition, ) -> Result<Vec<Stage>, StoreError>

The program that would exist on branch after transition is applied: the branch head (snapshot-cached) with the transition replayed over it, every resulting (sig, stage) bulk-loaded. Exact for a single-parent transition — the candidate Self::apply_operation_gated wants. Not valid for a merge: a StageTransition::Merge pins only the sigs the merge decided, while the op-DAG replay that computes a merge’s real head walks both parents (#833).

Source

pub fn apply_operation_gated( &self, branch: &str, op: Operation, transition: StageTransition, ) -> Result<OpId, StoreError>

Self::apply_operation_checked for a single-parent op where the caller holds only the transition: assembles the candidate via Self::candidate_program_for and runs the gate. Same rejection semantics — TypeError, a RepairHint attestation, head unchanged, nothing persisted. This is the write path for /v1/patch (#833). Merge ops must not use it (see candidate_program_for); they go through Self::apply_merge_op_gated.

Source

pub fn apply_operation_gated_with_intent( &self, branch: &str, op: Operation, transition: StageTransition, intent: Option<&Intent>, ) -> Result<OpId, StoreError>

Self::apply_operation_gated that also records intent (see Self::apply_operation_checked_with_intent) — the write path for an intent-carrying /v1/patch (#837 piece A).

Source

pub fn apply_merge_op_gated( &self, branch: &str, op: Operation, transition: StageTransition, ) -> Result<OpId, StoreError>

The gated write path for merge commits (commit_merge, POST /v1/merge/<id>/commit, lex merge commit).

A StageTransition::Merge pins only the sigs the merge decided (#1062); the sig->stage map every consumer reads is recomputed by replaying the op DAG, which for a merge walks both parents and can surface sigs the entries never mention. So the only way to know the true post-merge program is to replay it — land the op and read branch_head. This lands the merge op, type-checks the resulting head, and on a failure rolls the head back and returns TypeError.

Before #833 the merge paths landed through the ungated apply_operation, so a merge whose result didn’t compose (e.g. dst still calls helper, an agent-supplied resolution dropped it) advanced the head with nothing to catch it.

Rollback leaves the rejected merge op as an unreachable record (reclaimed by lex op gc, the same orphan crash-recovery already tolerates). A stage the merge names that was never published surfaces as the underlying StoreError from the bulk read — the “never advance onto content that can’t be loaded” invariant from the other side.

Source

pub fn apply_merge_op_gated_with_manifest( &self, branch: &str, op: Operation, transition: StageTransition, manifest: Option<&str>, intent_id: Option<&IntentId>, ) -> Result<OpId, StoreError>

Like Self::apply_merge_op_gated, but also appends the SetFiles a disagreeing-manifest merge needs (#1007 §1 / PR 7).

manifest is the already-computed, already-stored merged manifest blob id — see [crate::files::manifest_merge] plus a merge session’s file-conflict resolutions for how the caller builds it. None when dst’s and src’s manifests already agreed (the common case): nothing to record, behaves exactly like apply_merge_op_gated.

On any failure — the sig-level type-check gate (as before), or the follow-up SetFiles (which should essentially never fail here since the caller already validated the manifest before calling this, but a store can be modified concurrently) — the branch head is rolled all the way back to where it was before this call. A merge op is never left standing as a head with an Ambiguous manifest; that would violate the always-valid-HEAD invariant check_head_files polices for every other path onto a head.

Source

pub fn typecheck_merge_projection( &self, branch: &str, delta: &BTreeMap<String, Option<String>>, ) -> Result<(), StoreError>

Type-check the program that would result from overlaying a merge delta onto branch’s current head — without moving the head (#834). delta maps sig_id -> Some(stage) to set that sig to stage, or sig_id -> None to remove it, exactly the entries a StageTransition::Merge records.

This is the read-only, resolve-time counterpart of apply_merge_op_gated’s commit-time gate: it lets a merge session tell an agent which resolution broke type-checking the moment it is submitted, instead of only after a failed commit. Ok(()) means the projected program composes; a type failure is Err(StoreError::TypeError(..)); a read failure is the corresponding StoreError I/O variant.

Source

pub fn try_semantic_body_merge( &self, dst_branch: &str, sig_id: &str, base: &str, ours: &str, theirs: &str, ) -> Result<Option<String>, StoreError>

#838: attempt a typed three-way merge of a single sig’s body for a ModifyModify conflict — the intra-function, better-than-git case where two agents edited disjoint subtrees of the same function (different match arms, different let bindings).

base / ours (the dst side) / theirs (the src side) are the three stage ids the merge engine surfaced for sig_id. Loads the three FnDecls, structurally merges the bodies (lex_vcs::merge_bodies), and accepts the result only if the merged function also type-checks against dst_branch’s head — a body that composes syntactically but not by type is still a conflict (#838). On success the merged stage is published (content-addressed, idempotent; orphaned and GC-reclaimable if the merge is never committed) and its id returned; None means “fall back to a whole-function conflict.”

Deliberately narrow for this slice: only pure body divergence is merged. If the two sides disagree on anything but the body (examples, type params — the signature is identical by construction, since all three share sig_id), or either stage isn’t a function, it falls back to a conflict.

Source

pub fn replay_request(&self, op_id: &str) -> Result<ReplayRequest, StoreError>

#836 G3: assemble everything a regenerator needs to replay an op — re-derive the change from its recorded cause. Returns the op’s recorded intent (prompt / model / session), the target sig and the stage id it produced, and the program the change was made against (the parent state, rendered to source). An external harness feeds the prompt + parent program to the recorded model, then hands the regenerated stage back to Self::replay_compare (lex owns the deterministic comparison; the model call is the harness’s, matching the rest of the architecture).

Errors with UnknownOp if the op_id is unknown, or InvalidTransition if the op didn’t produce a stage (a removal / import / merge has nothing to regenerate). A SetFiles op (#1007) is refused by kind with NotReplayable::Files — typed, so replay coverage can leave it out instead of counting it as a miss.

Source

pub fn replay_compare( &self, op_id: &str, candidate: &Stage, ) -> Result<ReplayOutcome, StoreError>

#836 G3: compare a regenerated candidate against what the op recorded producing, and emit the Replay attestation. The reproducibility claim made concrete — a faithful regeneration of the same function from the same cause yields the same content-addressed stage id.

reproduced is true iff the candidate is the same sig and the same stage id the op recorded. A candidate for a different sig counts as “not reproduced” (produced_stage_id: None) rather than an error — it’s a legitimate, if negative, replay result. The attestation is addressed to the op’s recorded stage, so list_for_stage surfaces it alongside the TypeCheck/Examples evidence.

Source

pub fn replay_record_miss( &self, op_id: &str, reason: &str, ) -> Result<ReplayOutcome, StoreError>

Record a negative replay result for a regeneration that never yielded a comparable stage — the output didn’t parse, or didn’t define the target sig (#836 G3). Emits a Replay { reproduced: false, produced_stage_id: None } attestation with reason in its Failed detail, so an automated lex op replay run always records a verdict rather than aborting. reason is caller-supplied (e.g. “regenerated source did not parse”).

Source

pub fn replay_record( &self, op_id: &str, produced_stage_id: Option<String>, reproduced: bool, behavioral_samples: Option<usize>, fail_detail: Option<String>, ) -> Result<ReplayOutcome, StoreError>

Record a replay verdict the caller has already decided — used by the CLI’s behavioral tier, which does the (VM-backed) equivalence check the store deliberately can’t. expected_stage_id is looked up from the op. Set behavioral_samples to Some(n) when the candidate reproduced behaviorally over n sampled inputs rather than by exact stage-id match; the attestation then records that weaker-but-real claim distinctly.

Source

pub fn replay_stage_of( &self, op_id: &str, candidate: &Stage, ) -> Result<(String, Option<String>, bool), StoreError>

Compute the exact-match verdict for a candidate without emitting an attestation — (expected_stage_id, produced_stage_id, exact). Lets a caller (the CLI) fall back to a behavioral check on a valid but non-identical candidate and emit a single verdict, instead of Self::replay_compare’s emit-immediately shape.

Source

pub fn program_stages_at_op( &self, op_id: &str, ) -> Result<Vec<Stage>, StoreError>

The program at an op (that op and all its ancestors applied), as canonical stages. The behavioral replay tier needs the whole program — a regenerated function may call helpers from its parent state, so it can only be run in context. Exposed for the CLI’s equivalence check; op_id may be any op in the log.

Source

pub fn program_stages_at_op_skipping( &self, op_id: &str, ) -> Result<ReconstructedProgram, StoreError>

Self::program_stages_at_op, but head declarations whose stage can’t be loaded are skipped and reported instead of failing the reconstruction (#868).

Source

pub fn bare_declaration_name(name: &str) -> &str

See [demangled_name]; method form for call sites that already hold a Store.

Source

pub fn demangled_program_at_op( &self, op_id: &str, ) -> Result<Vec<Stage>, StoreError>

The program at an op as an author would write it — declarations under bare names (twice, not lib_<hash>.twice), with the head’s imports (#980).

A package publish mangles every declaration with a path-derived prefix. That prefix is a storage detail: a dotted name cannot be written as Lex source at all (fn lib_abc.twice(..) is a parse error), so anything that shows a program to an author — or asks one to regenerate a function from it — has to de-mangle first.

Falls back to the mangled reconstruction when de-mangling isn’t available (a multi-module head, pending #942), so callers degrade to the previous behaviour rather than failing outright.

Source

pub fn demangled_program_at_op_skipping( &self, op_id: &str, ) -> Result<ReconstructedProgram, StoreError>

Self::demangled_program_at_op, but head declarations whose stage can’t be loaded (GC’d, superseded, never persisted) are skipped and reported instead of failing the reconstruction (#868). This is what replay uses: a partial context is still worth replaying against, and the skips travel with the request and verdict.

Source

pub fn recompute_producer_trust( &self, tool_id: &str, window: usize, granted_by: &str, ) -> Result<Option<AttestationId>, StoreError>

Open the attestation log rooted at this store. The log lives under <root>/attestations/; opening is idempotent and cheap (fs::create_dir_all). Exposed publicly so consumers — lex blame --with-evidence, GET /v1/stage/<id>/attestations — can read what the store gate emitted without round-tripping through this crate’s API surface. Recompute a producer’s trust score from its recent attestation history and emit a fresh ProducerTrust attestation (#293). Score = `passed / (passed + failed

  • inconclusive)over the lastwindowattestations produced bytool_id, expressed in thousandths (0..=1000`).

Refuses to grant trust when the tool has an active ProducerBlock — the block wins as a hard veto. Returns Ok(None) for “no attestations to score” (a brand-new producer); the caller can choose how to handle it (typically: skip the publish until evidence accrues).

granted_by is the identity of the actor running the recompute (typically the human admin, or “lex-ci-bot” for an automated nightly).

Source

pub fn live_producer_trust_scores( &self, ) -> Result<BTreeMap<String, u32>, StoreError>

The latest live ProducerTrust score (thousandths, 0..=1000) for every producer that currently has trust: the newest score per tool by timestamp, excluding any tool under an active ProducerBlock (a block is a hard veto over trust, matching recompute_producer_trust).

Used to export a capsule trusted-keys keyring from earned trust — the producer id doubles as the publisher’s signing key downstream, so this turns track record into the allowlist capsule install consumes.

Source

pub fn attestation_log(&self) -> Result<AttestationLog, StoreError>

Source

pub fn verify_head_and_attest( &self, branch: &str, from_head: Option<&str>, to_head: &str, ) -> Result<HubCiVerdict, StoreError>

The hosted CI runner (#93): independently re-run the write-time type-check gate on a branch head and record the verdict as a lex-hub-ci-produced TypeCheck attestation for the stages the advance introduced. Called after an op push fast-forwards the head, so require-attestation type_check gates are backed by a producer that actually verified the code server-side, not by whatever attestation a client chose to attach. Does NOT move or roll back the head — the client’s own always-valid-HEAD gate is what refuses a bad publish; this produces the trusted verdict on top of an already-committed advance (so a client that bypassed its gate is caught by a TypeCheck::Failed from lex-hub-ci).

from_head is the branch head before the advance; the ops between it and to_head are the ones whose stages get attested. Idempotent: attestations are content-addressed, so re-verifying the same head is a no-op.

Source

pub fn record_examples_passed( &self, stage_id: &str, op_id: &OpId, count: usize, ) -> Result<(), StoreError>

Emit an Examples::Passed attestation for a published stage whose behavioral examples {} block was run and passed (#835, Tier 1). Mirrors Self::record_typecheck_passed. The behavioral run itself happens one layer up (lex-api / lex-cli) because it needs the bytecode compiler + VM, which this crate deliberately doesn’t depend on; the store only records the verdict. file_hash uses the stage id — the stage fully determines its own examples.

Source

pub fn record_review( &self, stage_id: &str, op_id: Option<OpId>, reviewer: &str, verdict: ReviewVerdict, notes: Option<String>, ) -> Result<AttestationId, StoreError>

Record a structured Review verdict on a stage (#836 G4). The verdict maps onto the attestation result so existing result-based tooling reads it: Approve->Passed, Reject->Failed, RequestChanges->Inconclusive.

Source

pub fn latest_review_verdict( &self, stage_id: &str, ) -> Result<Option<ReviewVerdict>, StoreError>

The latest Review verdict recorded on a stage, if any (#836 G4). “Latest” is arrival order in the attestation log — a server-assigned sequence number — NOT the attestation’s timestamp, which the writer chooses and which is excluded from the attestation id. So a verdict pushed with a far-future timestamp does not outrank one that actually arrived later. Legacy attestations with no arrival stamp order by timestamp among themselves and below every stamped one; see lex_vcs::AttestationLog::sort_by_arrival. Used by promote_candidate to honor a standing Reject.

This fixes ordering only. It does not authenticate who wrote a verdict: any writer that can reach the log can still append a later one.

Source

pub fn record_op_trace( &self, run_id: &str, root_target: &str, op_id: &OpId, result: AttestationResult, producer: ProducerDescriptor, ) -> Result<usize, StoreError>

Emit Trace attestations linking an already-committed op to the run that produced it (#257). One attestation per produced stage (matching the TypeCheck emission contract — see Self::apply_operation_checked) with op_id: Some(op_id) set, so lex trace --op <op_id> surfaces the run.

Returns the number of attestations emitted (zero for ops that produce no attestable stage, e.g. Remove / ImportOnly).

Idempotent: re-emitting for the same (run_id, root_target, op_id, stage_id, producer, result) tuple dedups via content addressing.

op_id must already exist in the op log — an unknown op surfaces as StoreError::UnknownOp.

Source

pub fn record_run_committed_ops_since( &self, run_id: &str, root_target: &str, branch: &str, base: Option<&OpId>, result: AttestationResult, producer: ProducerDescriptor, ) -> Result<usize, StoreError>

Walk ops_since(branch_head, base) and emit per-stage Trace attestations for each new op, linking them to the run that produced them (#257). Used by lex run --trace after the VM exits: snapshot base = branch_head before the run, then call this with the post-run head.

base = None means “every op currently reachable from the branch head” — generally not what you want for a single run; pass the pre-run head.

Returns the total number of attestations emitted across every new op. Zero is the common case (the run committed no ops).

Idempotent on the per-op level via Self::record_op_trace.

Source

pub fn apply_replace_match_arm( &self, branch: &str, from_stage_id: &str, match_node: &NodeId, arm_index: usize, new_body: CExpr, ) -> Result<OpId, StoreError>

Apply a typed ReplaceMatchArm transform (#280) and emit a OperationKind::ReplaceMatchArm op that records the semantic shape of the edit, not just the byte effect.

Steps:

  1. Load the source stage’s canonical bytes (delta-aware).
  2. Run lex_ast::replace_match_arm to produce the new Stage. Pure function, no I/O.
  3. Publish the new stage. Idempotent on the content-addressed to_stage_id.
  4. Assemble the candidate program (every active stage on the branch, with the rewritten one swapped in) and call Self::apply_operation_checked — re-typechecks and runs every existing gate (TypeCheck attestation, required_attestations, producer-block walk-back).

Failure modes:

  • StoreError::TransformError — transform didn’t apply. The branch is unchanged; no stage published.
  • StoreError::TypeError — transform produced an ill-typed program. The new stage is on disk (idempotent on its content hash) but the branch is unchanged. Same “publish without advance” semantics as #245.
  • Everything else from apply_operation_checked.
Source

pub fn apply_replace_match_arm_with_intent( &self, branch: &str, from_stage_id: &str, match_node: &NodeId, arm_index: usize, new_body: CExpr, intent: Option<&Intent>, ) -> Result<OpId, StoreError>

Self::apply_replace_match_arm with the write attributed to intent (#837 piece A): the op is stamped with the intent’s id and the intent is recorded once the gate has passed. None is the intent-less form.

Source

pub fn apply_rename_local( &self, branch: &str, from_stage_id: &str, let_node: &NodeId, new_name: &str, ) -> Result<OpId, StoreError>

Apply a typed RenameLocal transform (#280) — rename a let-bound local within a fn body and emit a matching OperationKind::RenameLocal. Same end-to-end shape as Self::apply_replace_match_arm; see that method for the failure-mode taxonomy.

Source

pub fn apply_rename_local_with_intent( &self, branch: &str, from_stage_id: &str, let_node: &NodeId, new_name: &str, intent: Option<&Intent>, ) -> Result<OpId, StoreError>

Self::apply_rename_local with the write attributed to intent (#837 piece A); see Self::apply_replace_match_arm_with_intent.

Source

pub fn apply_inline_let( &self, branch: &str, from_stage_id: &str, let_node: &NodeId, ) -> Result<OpId, StoreError>

Apply a typed InlineLet transform (#280) — eliminate a let x := v; body by substituting v for every unshadowed x in body, then replacing the Let node with the substituted body. Same end-to-end shape as Self::apply_replace_match_arm.

Source

pub fn apply_inline_let_with_intent( &self, branch: &str, from_stage_id: &str, let_node: &NodeId, intent: Option<&Intent>, ) -> Result<OpId, StoreError>

Self::apply_inline_let with the write attributed to intent (#837 piece A); see Self::apply_replace_match_arm_with_intent.

Source

pub fn apply_extract_function( &self, branch: &str, from_stage_id: &str, expr_node: &NodeId, spec: ExtractFnSpec, ) -> Result<(OpId, OpId), StoreError>

Apply a typed ExtractFunction transform (#280 slice 4) — extract a sub-expression of from_stage_id’s body into a new top-level fn defined by spec, and emit two ops tied together by a shared synthetic Intent so lex op log --intent <id> groups them.

The two ops:

  1. AddFunction { sig_id: <new_fn_sig>, stage_id: <new_fn_stage> }
  2. ModifyBody { sig_id: <source_sig>, from_stage_id, to_stage_id: <modified> }

The shared Intent’s prompt is structured (extract_function: <new_fn_name> plus the source identity) so downstream tooling can recover the typed-transform shape from the op-log + intent-log join.

Returns (add_fn_op_id, modify_body_op_id).

Source

pub fn apply_extract_function_with_intent( &self, branch: &str, from_stage_id: &str, expr_node: &NodeId, spec: ExtractFnSpec, supplied_intent: Option<&Intent>, ) -> Result<(OpId, OpId), StoreError>

Self::apply_extract_function with the write attributed to a caller-supplied intent (#837 piece A). Both ops carry that intent instead of the synthetic [lex.transform.extract_function] one — the typed shape is still recoverable from the op pair itself (AddFunction + ModifyBody under one intent). None keeps the synthetic intent.

Two ops means two gate passes, so this method makes the pair atomic itself: the final program (source rewritten + new fn) is checked before anything lands, and if the second op is refused anyway (a non-type gate) the head is rolled back to where it started. A rejected extraction therefore never leaves the new fn on the head without the call that uses it.

Source

pub fn propose_candidate( &self, branch: &str, new_stage: &Stage, intent_id: &IntentId, ) -> Result<OpId, StoreError>

Propose a stage for sig_id without advancing the branch head (#294). Multiple agents can call this concurrently for the same sig — every call lands a fresh Candidate op chained off the current head_op. The branch head stays where it was; a later Self::promote_candidate picks the winner.

The caller is responsible for typechecking new_stage against whatever program context they consider valid — propose_candidate doesn’t run the gate. Type errors surface at promotion time, where the candidate is composed back into a candidate program via the standard apply_operation_checked path.

The stage is published (idempotent on content hash). The intent_id is required so downstream consumers can distinguish proposals by author.

Source

pub fn list_candidates( &self, sig_id: &str, ) -> Result<Vec<CandidateInfo>, StoreError>

List every live Candidate op for sig_id — i.e. those not yet referenced by any Promote op (either as the winner or in the supersedes set). Used by lex stage candidates. Results are sorted by op_id for reproducibility.

Source

pub fn promote_candidate( &self, branch: &str, candidate_op_id: &OpId, ) -> Result<OpId, StoreError>

Promote a previously-landed Candidate op as the new branch head for its sig (#294). Emits a Promote op listing every other live Candidate for the same sig in its supersedes field. After this lands, Self::list_candidates returns an empty set for the sig.

Re-typechecks the candidate program (winner stage + the rest of the branch) through apply_operation_checked, so a candidate that doesn’t compose with the current branch state surfaces as StoreError::TypeError.

Source

pub fn apply_operation( &self, branch: &str, op: Operation, transition: StageTransition, ) -> Result<OpId, StoreError>

set_branch_head_op for the durability story on the branch file itself.

Source§

impl Store

Source

pub fn current_branch(&self) -> String

Source

pub fn set_current_branch(&self, name: &str) -> Result<(), StoreError>

Source

pub fn list_branches(&self) -> Result<Vec<String>, StoreError>

Source

pub fn get_branch(&self, name: &str) -> Result<Option<Branch>, StoreError>

Source

pub fn branch_head( &self, name: &str, ) -> Result<BTreeMap<String, String>, StoreError>

Computed view: walk the op log from the branch head and replay each transition into a SigId → StageId map.

Backed by a persisted snapshot (<branch>.head_snapshot.json) keyed on the head it was computed for. Steady state — this call’s head_op matches the last call’s — replays only the ops since the snapshot instead of the whole history: O(ops since the last call) instead of O(total branch history). Falls back to a full walk (and refreshes the snapshot) whenever there’s no snapshot yet, or the snapshot’s op isn’t actually an ancestor of the new head (a branch reset, or history reordered by a merge) — see OpLog::walk_forward_since’s own doc comment.

This existed as a genuine, measured bottleneck before the snapshot: a single call over a tenant with 110k+ accumulated ops took on the order of an hour, dominated by one disk read per ancestor op in the full BFS walk (alpibrusl/lex-lang#813’s follow-up). Every consumer that used to call this once per file in a multi-file publish (fixed separately, also #813) now calls it once per publish request — but “once” was still a full walk over the entire history every time, since nothing persisted the result between calls.

Source

pub fn branch_manifest(&self, name: &str) -> Result<ManifestAt, StoreError>

The files manifest at name’s head (#1007) — see Store::manifest_at. Cached in the head snapshot alongside the sig→stage map and extended the same way, so steady state costs O(ops since the last call).

Source

pub fn branch_log(&self, name: &str) -> Result<Vec<MergeRecord>, StoreError>

Source

pub fn create_branch(&self, name: &str, from: &str) -> Result<(), StoreError>

Snapshot the source branch’s head_op into a new named branch.

Source

pub fn create_predicate_branch( &self, name: &str, predicate: Value, ) -> Result<(), StoreError>

Create a predicate-defined branch (#133). The branch’s content is the set of ops matching predicate; head_op stays None and is materialized lazily by callers when they need a single point to apply ops against. Cheap to create and discard — it’s a saved query, not a snapshot.

Source

pub fn delete_branch(&self, name: &str) -> Result<(), StoreError>

Source

pub fn advance_branch_head_ff( &self, name: &str, new_head: &OpId, ) -> Result<BranchAdvance, StoreError>

Atomically set a branch’s head_op. Used by apply_operation after a successful op apply. Materializes main’s branch file on first call (creates branches/main.json).

Crash safety: the tempfile’s data is fsync’d before rename (see write_branch_atomic), so a successful return implies a durable branch file at the final path. The containing directory is not fsync’d; on a crash between rename and the directory’s metadata flush, the rename can be lost — the prior head (or missing branch file for a fresh main) survives. The op record itself is content-addressed and is independently durable in the op log.

Concurrency: single-writer per store. Two writers calling this for the same branch race on read-modify-write of the JSON file (each reads, mutates head_op, renames its tempfile in). Last writer wins; the loser’s head update is silently dropped, even though both their op records survive in the op log. Tier-1 merge / lex publish callers run sequentially; multi-writer safety (file locking) is on the table once lex serve becomes a real concurrent producer (#130 territory). Advance branch to new_head, fast-forward only — the ref half of op push (the op objects are transferred separately via the ops batch). Semantics mirror git push to a branch:

  • branch absent / no head yet → create it at new_head;
  • new_head already the head → no-op (UpToDate);
  • current head is an ancestor of new_head → fast-forward;
  • otherwise → StoreError::NonFastForward, so a disjoint or diverged history can’t silently clobber a shared branch.

new_head must already exist in the op log (the batch landed it); an unknown op is a NonFastForward against a head it can’t reach.

Source

pub fn invalidate_gate_checkpoints(&self) -> Result<usize, StoreError>

Invalidate every branch’s last_gate_checkpoint (#256). Run when a new ProducerBlock attestation lands so the next branch advance walks back from genesis once and re-verifies the full chain. Returns the number of branches whose checkpoint changed.

Source§

impl Store

Source

pub fn merge(&self, src: &str, dst: &str) -> Result<MergeReport, StoreError>

Source

pub fn sig_map_at_op( &self, op_id: &str, ) -> Result<BTreeMap<String, String>, StoreError>

The SigId → StageId map at op_id: the full replay of that op’s ancestry in OpLog::walk_forward’s canonical order, computed fresh (never through a branch’s incremental snapshot). This is the single definition of “the head at an op”; branch_head must agree with it.

Source

pub fn merge_pins( &self, dst_head: Option<&OpId>, src_head: Option<&OpId>, auto_resolved: &[MergeOutcome], resolved: &[(ConflictId, Resolution)], ) -> Result<BTreeMap<String, Option<String>>, StoreError>

The Merge entries that pin every sig a merge decided to the value it decided, so the merged head is a function of the resolved merge and not of the order the two parallel histories are replayed in (#1062).

A merge op is replayed by re-applying both parents’ ancestries and then its own entries. Both sides’ ops on a sig the merge decided are concurrent and do not commute (a rename retires the sig a modify rebinds), so whichever the replay happens to apply last used to win — and a resolution that leaves the sig as dst already has it (take_ours, or a sig only dst touched) changes nothing relative to dst, so it was never listed and nothing outranked the replay. Listing it here does.

  • Src outcomes: src’s stage (None: src removed it).
  • Dst / Both outcomes and TakeOurs: the sig as dst’s head has it (None: dst lacks it, so the merge keeps it absent).
  • TakeTheirs: the sig as src’s head has it.

Custom resolutions carry their target in the op itself; callers add them on top (and own the error for an op that has none).

Source

pub fn commit_merge( &self, dst: &str, report: &MergeReport, ) -> Result<(), StoreError>

Source§

impl Store

Source

pub fn plan_gc(&self, cli_retain: &[Predicate]) -> Result<GcPlan, StoreError>

Build a GcPlan from the store’s current state plus an optional list of additional retention predicates from the CLI (lex op gc --retain ...). The policy file’s gc_retention.retain entries are appended to those.

Returns StoreError::Io(InvalidData, ...) if a predicate in policy.json fails to parse.

Source

pub fn apply_gc(&self, plan: &GcPlan) -> Result<usize, StoreError>

Apply a GcPlan — actually delete every op in plan.to_delete. Idempotent: running again on the same store after a successful apply yields a plan with an empty deletion set.

Returns the number of op records actually removed (loose files deleted + packed ops dropped during pack rewrites).

Source§

impl Store

Source

pub fn plan_blob_gc(&self, grace: Duration) -> Result<BlobGcPlan, StoreError>

Build a BlobGcPlan: mark every blob reachable from a retained SetFiles op’s manifest plus every blobrefs/** binding, then sweep everything else older than grace. See the module docs above for why the grace period exists and why “retained” mirrors Self::plan_gc rather than recomputing branch reachability independently.

Source

pub fn apply_blob_gc(&self, plan: &BlobGcPlan) -> Result<usize, StoreError>

Apply a BlobGcPlan — delete every blob in plan.to_delete. Idempotent: a blob already gone (deleted by a concurrent GC pass, e.g. another replica) is not an error. Returns the number of blobs actually removed.

Source§

impl Store

Source

pub fn put_manifest(&self, manifest: &Manifest) -> Result<BlobId, StoreError>

Validate manifest and store it as a blob. Returns its id.

Source

pub fn get_manifest(&self, id: &str) -> Result<Manifest, StoreError>

Load and validate the manifest stored under id.

Source

pub fn manifest_closure_missing(&self, manifest: &Manifest) -> Vec<BlobId> ⓘ

The entry blobs of manifest this store does not hold, sorted and deduplicated. Empty means the manifest’s closure is complete.

Source§

impl Store

Source

pub fn manifest_at(&self, op_id: &str) -> Result<ManifestAt, StoreError>

The files manifest in force at op_id (#1007): its own if it is a SetFiles, else inherited through its parents — see ManifestAt. UnknownOp if op_id is not in the log.

Source

pub fn validate_set_files(&self, manifest: &str) -> Result<Manifest, StoreError>

Check that manifest may become the file set of a head: it decodes as a canonical, valid manifest (no reserved src/**/*.lex paths), every entry blob is present, and each blob’s length matches its entry’s size. Writes nothing.

Source

pub fn apply_set_files( &self, branch: &str, manifest: &str, intent_id: Option<&IntentId>, ) -> Result<OpId, StoreError>

Append a SetFiles { manifest } op to branch (#1007): the repository’s non-op-log files become exactly the snapshot manifest names. The sig→stage map is untouched.

Validated first (Self::validate_set_files); on any failure nothing is persisted and the head is unchanged. Goes through the same CAS advance (and branch-advance policy gate) as every other op. Always appends: a caller that wants “no op when unchanged” compares against Self::branch_manifest first.

Source§

impl Store

Source

pub fn manifest_merge( &self, base_head: Option<&str>, ours_head: Option<&str>, theirs_head: Option<&str>, ) -> Result<ManifestMergeOutcome, StoreError>

3-way-merge the files manifests of a merge’s ours (dst) and theirs (src) heads against their base (the merge’s LCA) — the files-dimension counterpart of [crate::merge::merge] for sigs.

Ok(NoChange) when ours and theirs already carry the exact same ManifestAt (including both Absent — a package with no files at all) — the merge needs no SetFiles, matching #1007 §1’s “files agree ⇒ no SetFiles” case. Otherwise Ok(Needed { .. }): auto_entries holds every path that resolved without a conflict (already reflecting the winning side, or absent if both sides agree the path is gone); conflicts lists the paths that need an explicit lex_vcs::FileResolution.

Err(AmbiguousManifest) if ours or theirs is itself Ambiguous — an earlier merge in that side’s history landed without the SetFiles §1 requires. There is no well-defined manifest to diff against on that side, so this merge refuses too; see StoreError::AmbiguousManifest.

Source

pub fn build_merged_manifest( &self, auto_entries: BTreeMap<String, Entry>, conflicts: &[FileConflict], resolutions: &BTreeMap<FilePath, FileResolution>, merge_op_id: &str, ) -> Result<BlobId, StoreError>

Build the final merged Manifest once every conflict Self::manifest_merge surfaced has a resolution (#1007 PR 7): auto_entries (unconditionally included) plus, per conflict, the resolved side’s entry (omitted entirely if that side had none — i.e. the resolution keeps a removal). Stores the manifest as a blob and returns its id, ready for Store::apply_merge_op_gated_with_manifest.

Callers are expected to have already rejected any lex_vcs::FileResolution::Defer (the same contract MergeSession::commit enforces before this is ever called) — Defer here is treated the same as no resolution: the conflict’s path is simply left out of the merged manifest, which would silently drop content, so this returns StoreError::AmbiguousManifest instead to fail loud.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Store

§

impl !UnwindSafe for Store

§

impl Freeze for Store

§

impl Send for Store

§

impl Sync for Store

§

impl Unpin for Store

§

impl UnsafeUnpin for Store

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.