Skip to main content

Crate lex_vcs

Crate lex_vcs 

Source
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-diff output. Lives in lex-vcs so both the CLI (which produces it) and diff_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.
AttestationLog
Persistent log of Attestation records.
ConflictRecord
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.
DiffInputs
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.
IntentLog
Persistent log of Intent records. Mirrors crate::OpLog’s shape: one canonical-JSON file per intent, atomic writes via tempfile + rename, idempotent on re-puts.
MergeOutput
MergeSession
Stateful merge in flight. Hold one per active merge between start and commit. Sessions are not thread-safe; the HTTP wrapper is expected to wrap them in a Mutex keyed by MergeSessionId.
ModelDescriptor
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 OpId is computed from this plus a sorted view of parents.
OperationRecord
An operation paired with its computed OpId and the resulting stage transition. This is what gets persisted under <root>/ops/<OpId>.json once apply.rs (next slice of #129) lands.
ProducerDescriptor
Who produced this attestation. tool is the CLI / harness name ("lex check", "lex agent-tool", "ci-runner@v3"). version pins the tool revision so a regression in the producer is distinguishable from a regression in the code being verified. model is set when an LLM was the proximate producer — for --spec-style runs the harness is the producer; for lex agent-tool the model is, and we want both recorded.
ResolveVerdict
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§

ApplyError
AttestationKind
What was verified. The variants mirror the verdict surfaces lex agent-tool and the store-write gate already produce.
AttestationResult
Whether the verification succeeded. Inconclusive is 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.
CommitError
Why a commit failed. Conflicts-remaining is the most common case — agents are expected to iterate via resolve until this goes away.
ConflictKind
DiffMappingError
GateError
MergeOutcome
OperationKind
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 existing OpIds stay stable.
Predicate
A saved query over the operation log. Evaluating against an OpLog returns the matching OperationRecords.
Resolution
Choice for a single conflict.
ResolutionRejection
Why a resolution was rejected. Distinct from CommitError because a resolve call returns per-conflict verdicts; commit returns a single overall verdict.
SpecMethod
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 pull spec-checker in.
StageTransition
Effect of applying an operation on a stage’s content-addressed identity. Used as the produces field of an OperationRecord so consumers can answer “after this op, what’s the head stage for this SigId?” without rerunning the apply step.

Traits§

IntentResolver
Resolver the evaluator uses to look up intent metadata when a Session clause needs to know “which intents belong to this session?”. Wrapping it as a trait object lets test code stub it without standing up a real crate::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::TypeError with 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 just io. 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. Session clauses are resolved as if no intent had a session — most callers go through evaluate_with_resolver instead. Use this entry point when the predicate is known not to reference sessions.
evaluate_with_resolver
Evaluate with a caller-provided IntentResolver for Session clauses. 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§

AttestationId
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).
ConflictId
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.
ContentHash
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.
EffectSet
Sorted set of effect-kind strings (e.g. ["fs_write", "io"]). BTreeSet so the canonical form is order-independent for hashing.
ImportMap
IntentId
Content-addressed identity of an intent. Lowercase-hex SHA-256 of the canonical form of (prompt, session_id, model, parent_intent). Excludes created_at so two runs of the same prompt produce the same id.
MergeSessionId
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.
ModuleRef
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 in lex-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 identical OpIds; the store dedupes on this.
SessionId
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-store uses; we keep it as String here so this crate has no dependency on lex-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.