weavatrix-refactor-plan 0.1.0

Evidence metadata, validation profiles, and canonical fingerprints for Weavatrix refactor plans
Documentation

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 original from source, 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.

{
  "schemaVersion": "weavatrix.refactor-plan.v1",
  "operation": "move_and_generate",
  "operations": [
    {
      "kind": "rename",
      "value": {
        "from": "src/old.rs",
        "to": "src/new.rs",
        "expectedSourceSha256": "0000000000000000000000000000000000000000000000000000000000000000"
      }
    },
    {
      "kind": "create",
      "value": { "path": "src/generated.rs", "contents": "// generated\n" }
    }
  ]
}

Quick start

use weavatrix_refactor_plan::{
    CreateFile, RefactorOperation, RefactorPlan, RefactorPlanLimits,
    validate_executor_plan,
};

# fn main() -> Result<(), Box<dyn std::error::Error>> {
let plan = RefactorPlan::new(
    "generate",
    vec![RefactorOperation::Create(CreateFile::new(
        "src/generated.rs",
        "pub const GENERATED: bool = true;\n",
    ))],
);

let checked = validate_executor_plan(&plan, RefactorPlanLimits::default())?;
println!("{}", checked.fingerprint());
# Ok(())
# }

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:

  • EvidenceScope has an open kind, a stable value, optional portable roots, and languages;
  • PlannerIdentity identifies planner name/version and backend, with an optional backend version;
  • graphRevision preserves missing, explicit null, 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 3339 createdAt, explicit graphRevision, 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, .git and .weavatrix exclusion, 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 to Modify and preserves completeness and every top-level extension;
  • try_into_text_edit_plan succeeds only when every operation is Modify.

The lower-level crate is also available as weavatrix_refactor_plan::weavatrix_edit.

Conformance evidence

  • docs/schema/weavatrix.refactor-plan.profile.v1.schema.json describes the strict producer profile and all four operation values.
  • tests/fixtures/refactor-plan-v1.jsonl contains Node-generated canonical JCS payloads and fingerprints checked by Rust.
  • tests/fixtures/generate-refactor-plan-v1.mjs is the independent Node oracle.
  • tests/fixtures/refactor-plan-conformance.jsonl binds raw JSON cases to the runtime consumer/planner results.
  • tests/fixtures/js-v0.1.5-ownership.json accounts 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

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 1.10x–1.68x 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 — on the same 500-file plan a model that drops them decodes in 300 µs where the eager one takes 1,284 µs — 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, and lazy extension capture is the open optimization.

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.