weavatrix-refactor-plan 0.1.1

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:

```text
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.

```json
{
  "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

```rust
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:

```text
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`]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 |

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](https://github.com/sergii-ziborov/weavatrix-edit/blob/main/docs/decoder-comparison.md)).
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`](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`](tools/benchmarks) and [`docs/benchmarks.md`](docs/benchmarks.md).

## MSRV and license

Rust 1.88 or newer, edition 2024. MIT licensed.