Expand description
Agent-native version control for Lex (#128 tier-2).
The unit of writing is an Operation — a typed delta on the AST
identified by (kind, payload, parents). Two agents producing the
same logical change against the same parent state get the same
OpId, so the store can dedup automatically and surface “we
agree” without a merge.
This crate is the foundation slice of #129. It defines the operation enum and content-addressed identity. Subsequent slices add: applying ops to a store state (#129 cont’d), the write-time type-check gate (#130), intent linkage (#131), attestations (#132), predicate branches (#133), and the programmatic merge API (#134).
§Identity
OpId is the lowercase-hex SHA-256 of the canonical JSON form
of (kind, payload, parents). The serializer is deterministic
by construction (struct fields are emitted in declaration order;
EffectSet is a BTreeSet; parents are sorted before
hashing), so two independent runs producing the same logical
operation produce byte-identical canonical bytes.
lex-store already uses SHA-256 (via the sha2 crate) for stage
and signature identity, so we reuse that here for consistency
and to avoid pulling in a second hash dependency. The issue text
mentions Blake3; if that becomes load-bearing for performance we
can swap with a one-line crate change since OpId is opaque.
Re-exports§
pub use diff_report::DiffReport;
Modules§
- diff_
report - Plain-data shape of the
lex ast-diffoutput. Lives in lex-vcs so both the CLI (which produces it) anddiff_to_ops(which consumes it) can share types without a cyclic dep.
Structs§
- Attestation
- The persisted attestation. See module docs for what each field is, what’s in the hash, and what isn’t.
- Attestation
Log - Persistent log of
Attestationrecords. - Conflict
Record - Snapshot of one conflict the agent needs to resolve.
- Cost
- Optional cost record. Excluded from the attestation hash so rerunning a verification on a different machine (different wall-clock, different token pricing) doesn’t break dedup.
- Diff
Inputs - Intent
- The persisted intent. Carries the prompt that caused some operations to be produced, the model that interpreted it, and the session that grouped them. Many ops can share one intent; duplicating the prompt on each would be wasteful and break the “two equal ops hash equal” invariant.
- Intent
Log - Persistent log of
Intentrecords. Mirrorscrate::OpLog’s shape: one canonical-JSON file per intent, atomic writes via tempfile + rename, idempotent on re-puts. - Merge
Output - Merge
Session - Stateful merge in flight. Hold one per active merge between
startandcommit. Sessions are not thread-safe; the HTTP wrapper is expected to wrap them in aMutexkeyed byMergeSessionId. - Model
Descriptor - Which model produced the intent. Tracked so audit / blame can answer “what model wrote this?” without joining against an external table.
- NewHead
- OpLog
- Operation
- The operation as a whole — its kind and the causal predecessors
it assumes. The
OpIdis computed from this plus a sorted view ofparents. - Operation
Record - An operation paired with its computed
OpIdand the resulting stage transition. This is what gets persisted under<root>/ops/<OpId>.jsononceapply.rs(next slice of #129) lands. - Producer
Descriptor - Who produced this attestation.
toolis the CLI / harness name ("lex check","lex agent-tool","ci-runner@v3").versionpins the tool revision so a regression in the producer is distinguishable from a regression in the code being verified.modelis set when an LLM was the proximate producer — for--spec-style runs the harness is the producer; forlex agent-toolthe model is, and we want both recorded. - Resolve
Verdict - Per-conflict outcome of a resolve call.
- Signature
- Optional Ed25519 signature over the attestation hash. Verifying it is the consumer’s job; the data layer just stores the bytes.
Enums§
- Apply
Error - Attestation
Kind - What was verified. The variants mirror the verdict surfaces
lex agent-tooland the store-write gate already produce. - Attestation
Result - Whether the verification succeeded.
Inconclusiveis its own state because some checkers (e.g. random-sampling spec checks over an unbounded input space) can pass within their budget without proving the contract holds in general. - Commit
Error - Why a commit failed. Conflicts-remaining is the most common case — agents are expected to iterate via resolve until this goes away.
- Conflict
Kind - Diff
Mapping Error - Gate
Error - Merge
Outcome - Operation
Kind - The kinds of operations that produce stage transitions. Mirrors
the initial set in #129; new kinds (
MoveBetweenFiles,SplitFunction,ExtractType) can be added later as long as they’re appended at the end of this enum or use explicit#[serde(rename = "...")]tags so existingOpIds stay stable. - Predicate
- A saved query over the operation log. Evaluating against an
OpLogreturns the matchingOperationRecords. - Resolution
- Choice for a single conflict.
- Resolution
Rejection - Why a resolution was rejected. Distinct from
CommitErrorbecause a resolve call returns per-conflict verdicts; commit returns a single overall verdict. - Spec
Method - Verification method for
AttestationKind::Spec. Mirrors the tag the spec checker already uses — kept as a string so the vcs crate doesn’t have to pullspec-checkerin. - Stage
Transition - Effect of applying an operation on a stage’s content-addressed
identity. Used as the
producesfield of anOperationRecordso consumers can answer “after this op, what’s the head stage for this SigId?” without rerunning the apply step.
Traits§
- Intent
Resolver - Resolver the evaluator uses to look up intent metadata when a
Sessionclause needs to know “which intents belong to this session?”. Wrapping it as a trait object lets test code stub it without standing up a realcrate::IntentLog.
Functions§
- apply
- Apply an operation against a branch head and persist it.
- check_
and_ apply - Apply an operation only if the resulting candidate program
typechecks. Otherwise return
GateError::TypeErrorwith the structured error envelope; nothing is persisted. - compute_
diff - Compute a structural diff between two named fn-decl maps.
- diff_
to_ ops - effect_
label - Render an effect with its arg if present:
fs_read("/tmp"),net("api.example.com"), or justio. Used by both signature rendering and effect-diff so the same string identifies the same effect in either context. - evaluate
- Evaluate a predicate against an op log.
Sessionclauses are resolved as if no intent had a session — most callers go throughevaluate_with_resolverinstead. Use this entry point when the predicate is known not to reference sessions. - evaluate_
with_ resolver - Evaluate with a caller-provided
IntentResolverforSessionclauses. The returned vector is in the order the underlying candidate scan produced — typically newest-first when the candidate set is an ancestry walk, undefined when it’s a full log scan. - is_
stage_ blocked - Walk a stage’s attestations and return whether the latest
Block/Unblock decision is currently a Block. Used by
activation paths (e.g.
lex stage pin) to refuse when a human has signalled the stage shouldn’t ship. - merge
- render_
signature
Type Aliases§
- Attestation
Id - Content-addressed identity of an attestation. Lowercase-hex
SHA-256 of the canonical form of
(stage_id, op_id, intent_id, kind, result, produced_by). - Conflict
Id - Stable id for a conflict within a session. We use the SigId as
the conflict id since conflicts are 1:1 with the sigs that have
MergeOutcome::Conflict. If a future merge ever produces multiple conflicts on the same sig, this becomes a tuple. - Content
Hash - Content hash of a file (examples list, body source, etc.). Kept
as a string for the same reason as
OpId: we want this crate to have no view into the hash function used by callers. - Effect
Set - Sorted set of effect-kind strings (e.g.
["fs_write", "io"]).BTreeSetso the canonical form is order-independent for hashing. - Import
Map - Intent
Id - Content-addressed identity of an intent. Lowercase-hex SHA-256
of the canonical form of
(prompt, session_id, model, parent_intent). Excludescreated_atso two runs of the same prompt produce the same id. - Merge
Session Id - Stable id for a merge in flight. Caller-supplied so the HTTP surface can map URLs to sessions without leaking session ids from the engine. Production callers will likely use UUIDs; tests use short strings.
- Module
Ref - Reference to an imported module — either a stdlib name
(
std.io) or a local path (./helpers). Kept as a string so this crate doesn’t pull inlex-syntax’s parser. - OpId
- Identity of an operation.
(kind, payload, parents)SHA-256 in lowercase hex (64 chars). Two operations with identical payloads and parent sets produce identicalOpIds; the store dedupes on this. - Session
Id - Groups intents from the same agent session. Free-form string so callers can use whatever session model their harness has.
- SigId
- Signature identity of a function or type — the part that stays
stable across body edits. Wraps the same string identity
lex-storeuses; we keep it asStringhere so this crate has no dependency onlex-store’s internals. - SpecId
- Reference to a spec file. Free-form string so callers can use either a content hash or a logical name; the data layer doesn’t care which. Producers should pick one and stick with it for dedup to work as expected.
- StageId
- Content hash of a single stage (function body, type def, …).
Same string identity as the file under
<root>/stages/<SigId>/ implementations/<StageId>.ast.json.