weavatrix-refactor-plan
The protocol-independent, filesystem-independent contract for exact Weavatrix refactor plans.
The dependency direction is deliberate:
weavatrix-edit <- weavatrix-refactor-plan <- weavatrix-worktree
weavatrix-edit owns exact source edits. This crate owns the multi-operation
plan, evidence, validation, and fingerprint. weavatrix-worktree executes an
already validated plan transactionally. MCP, LSP clients, planners, sessions,
locks, confirmation tokens, journals, and rollback do not belong here.
Contract
RefactorPlan uses schema weavatrix.refactor-plan.v1 and supports:
Modify(FileEdit): exact text edits guarded by the whole-file SHA-256;Create(CreateFile): exact UTF-8 contents at a path that must be absent;Delete(DeleteFile): deletion guarded by the whole-file SHA-256;Rename(RenameFile): an exact source hash, absent destination, and optional exact edits against the originalfromsource, applied before it is moved.
TextEdit coordinates are frozen as 1-based lines and 0-based UTF-16 code
units. This is part of v1 and is not negotiated from an LSP client.
The operations array is one simultaneous transition set, not a sequential
script. Rename chains and cycles snapshot each original input; an executor's
deterministic commit order must not expose intermediate array states. Array
order is still retained and fingerprinted for stable evidence and
operationIndex references.
Operations use Serde's adjacent kind / value wire shape. The value is not
an arbitrary payload: it is the typed contract for that operation.
Quick start
use ;
#
The minimal constructor is intentionally executable. Semantic completeness is
not a filesystem safety prerequisite: a reviewed PARTIAL plan can still
contain exact, safely applicable operations.
Evidence and completeness
Evidence is typed in Rust and flattened at the top-level JSON object. Unknown extension fields are preserved, bounded, validated for reserved-key collisions, and included in the fingerprint.
CompletenessProof is machine-comparable rather than a free-form label:
EvidenceScopehas an openkind, a stablevalue, optional portable roots, and languages;PlannerIdentityidentifies planner name/version and backend, with an optional backend version;graphRevisionpreserves missing, explicitnull, and string states.
UncertainReference must identify a safe repository path (the legacy file
spelling is accepted) or a typed subject, and must include a kind or reason.
NotModified must identify a path, typed subject, or valid operationIndex,
and must include a reason. Portable aliases and duplicate evidence entries are
rejected.
There are three entry points:
validate_consumer_plan: exact operations plus internal consistency of any recognized evidence;validate_executor_plan: the same exact-operation safety contract, exposed as an executor-specific validated wrapper;validate_planner_plan: a strict producer profile requiring completeness, RFC 3339createdAt, explicitgraphRevision, typed proof, explicit evidence arrays, and a follow-up.
Strict PARTIAL output needs at least one uncertainty or omission. Any explicit
COMPLETE claim needs a typed proof and cannot contain either.
Safety and resource contract
Validation is pure; it does not open the repository. Before a plan can be fingerprinted or handed to an executor it checks:
- schema, operation and operation-count bounds;
- repository-relative paths,
.gitand.weavatrixexclusion, Windows device names, case/Windows suffix aliases, and Unicode NFC aliases; - exact lowercase SHA-256 preconditions;
- duplicate input/output roles, unsafe cross-operation overlaps, rename aliases, and per-file overlapping text ranges;
- edit counts/text, create bytes, distinct paths, evidence text/counts, and one combined byte/node/depth budget for every extension map;
- reserved flattened keys at plan, operation, edit, proof, scope, planner, uncertainty, omission, and subject levels.
Use parse_refactor_plan for untrusted JSON. It caps input before allocation,
limits recursion/value count, rejects duplicate object names recursively before
typed deserialization, and then runs normal validation. Generic JSON-to-map
parsing is not an equivalent security boundary because duplicate names have
already been collapsed.
Fingerprint contract
FINGERPRINT_ALGORITHM is
weavatrix.refactor-plan.jcs-sha256.v1. The digest is:
SHA-256(UTF-8(algorithm) || 0x00 || RFC-8785-JCS(plan without top-level createdAt))
The implementation uses a crate-owned streaming RFC 8785 serializer (ECMAScript
number formatting via ryu-js), orders keys by UTF-16 code units, and streams
sorted top-level fields into SHA-256. The operations array is not
materialized as one canonical byte vector. It rejects negative zero and exact
integers outside [-9007199254740991, 9007199254740991] before JCS because
those inputs are not stable I-JSON values.
Only the top-level createdAt is excluded: it records when the same plan was
produced, not what it proves or executes. A nested field named createdAt is
ordinary extension evidence and remains fingerprinted. Every operation,
precondition, completeness claim, proof, warning, and unknown extension remains
inside the digest.
canonical_plan_bytes exists for diagnostics and golden tests. Production code
should retain the PlanFingerprint returned by a validated wrapper.
Legacy text plans
The frozen weavatrix.edit-plan.v1 types remain re-exported. Explicit,
lossless text-only helpers avoid confusing the two schemas:
RefactorPlan::from_text_edit_plan(EditPlan)maps each file toModifyand preserves completeness and every top-level extension;try_into_text_edit_plansucceeds only when every operation isModify.
The lower-level crate is also available as weavatrix_refactor_plan::weavatrix_edit.
Conformance evidence
docs/schema/weavatrix.refactor-plan.profile.v1.schema.jsondescribes the strict producer profile and all four operation values.tests/fixtures/refactor-plan-v1.jsonlcontains Node-generated canonical JCS payloads and fingerprints checked by Rust.tests/fixtures/generate-refactor-plan-v1.mjsis the independent Node oracle.tests/fixtures/refactor-plan-conformance.jsonlbinds raw JSON cases to the runtime consumer/planner results.tests/fixtures/js-v0.1.5-ownership.jsonaccounts for all 175 legacy JavaScript tests without claiming that this crate owns planner, LSP, session, MCP, or transaction behavior.
Measured performance
Recorded on a frozen tree against the direct predecessor, npm
weavatrix-refactor@0.1.5, with canonicalize@3.0.0 as the fingerprint oracle.
Correctness gates run before any timing: canonical JCS bytes and the
domain-separated digest must match the Node oracle exactly. Medians come from 30
samples per cell inside warmed persistent workers; the conservative column is
the JavaScript p25 divided by the Rust p75.
| Class | 500-file plan, JS -> Rust median | Conservative ratio | Scope |
|---|---|---|---|
| Cached fingerprint lookup | 0.005 -> 0.000 µs | 11.19x | Equal semantics |
| End-to-end validate + fingerprint | 3570 -> 1092 µs | 2.44x | Rust validates strictly more |
| Legacy envelope validation | 370 -> 233 µs | 1.27x | Equal declared subset |
| Parse + legacy validation | 855 -> 1018 µs | 0.65x | Equal declared subset |
These numbers were recorded against weavatrix-edit 0.1.5. That dependency has
since replaced its derived envelope codecs with hand-written ones, which sits
inside the timed region of the two decode-bearing classes, so the table stands
until a publication rerun replaces it.
Parse plus validation is the one class where this crate is slower, and only at
the largest size. The cause is not the JSON decoder: measured fairly, under a
production profile, blazingly-json decodes faster than serde_json on every
realistic corpus
(evidence).
The cost is that this class materializes an extension map per file and per edit
for members the workload never reads — skipping them decodes the same 500-file
plan roughly twice as fast — on top of path-alias canonicalization,
reserved-key scans, and size budgets that JSON.parse plus the npm validator
do not perform at all. So the row measures strictly more work.
Skipping those members is available upstream as DeclaredEditPlan, and this
crate deliberately does not use it. Plan annotations live in undeclared
members, and the crate's own evidence budget and reserved-key checks walk the
extension maps, so a declared-only decode here would be a validation bypass
rather than an optimization: an oversized annotation blob that is rejected
today would pass. tests/declared_decode.rs pins that, and
docs/benchmarks.md records the decision.
The product route this crate exists for, validating a plan and binding it to a canonical fingerprint, is 3.3x faster at that size and 2.4x on the conservative gate.
A row states a "≥2.0x" claim only when its scope is declared equal and its
conservative ratio clears 2.0; stronger or mismatched scopes stay ineligible
regardless of ratio, and losing rows stay visible. This is not a universal
ranking. The harness, policy, and raw samples live in
tools/benchmarks and docs/benchmarks.md.
MSRV and license
Rust 1.88 or newer, edition 2024. MIT licensed.