Legacy software modernization

Modernize the software.
Preserve the business behavior.

Replacing an API or backend means deciding what must keep working. Proof helps recover those requirements, verify the replacement and make the remaining gaps visible.

Work alongside your migration team, or scope implementation with us.

A local LiteLLM migration POC · rerun 14 October 2026

The same malformed input.
Two different outcomes.

In a Python-to-Rust port of OCR response handling, a numeric pages value should not quietly become a successful empty result. We sent the same synthetic response through both implementation shims.

Shared input · malformed page data{"response": {"pages": 42, "model": "mistral-ocr-2505"}, "model": "mistral-ocr-2505"}

Python implementation

Rejected

ValidationError
pages: Input should be a valid list

The typed response rejects a number where a list is required.

Rust port

Returned success

{"ok": true, "output": {"pages": []}}

Non-array page data becomes an empty list. The success envelope is an excerpt of the actual result.

A compatibility decision, not just a code difference.

The retained failure requirement calls for rejecting malformed response data. This port changes rejection into success. The finding remains open; this is not a completed migration or a production incident.

Scope: two local response-transform cases at POC revision 207f407a7781. The nominal case also differs in null versus omitted fields. No live provider call or full-application equivalence was established. The existing failure fixture’s requirement link still needs correction; the run record preserves this limitation.

See where each difference comes from.

The mirrors view groups issues introduced by the replacement, inherited from the original, and fixed in the port. Each finding connects back to a requirement.

Proof Mistral OCR Python-to-Rust mirror, in progress. Source and target paths appear above findings grouped as the target’s own issues, inherited from the source, diverging on the target, and fixed in the target.
Saved POC dashboard snapshot. Counts may differ from the current view. Swipe to inspect, or open full size ↗Explore the live mirrors →

What must survive the rewrite?

Recover the agreement.
Check the replacement against it.

Existing code shows what happens today. Your product owner, contracts and requirements determine which behavior to preserve and which to change.

  1. Recover the important behavior

    Connect code, contracts, tests and earlier decisions. Bring undocumented rules and contradictory expectations to the person who can resolve them.

  2. Define what counts as compatible

    Record preserved behavior, intentional changes and open questions. Include errors, permissions, state changes and boundary cases, alongside successful responses.

  3. Run and explain the checks

    Compare the implementations and check agreed requirements. Each material difference needs a disposition and evidence. Passing sampled inputs does not prove every possible behavior.

  4. Keep the agreement for the next change

    Retain the requirements, fixtures, results and decisions. Later changes can reuse that work and identify which evidence must be renewed.

Two ways to work together

Start with one component.

Verify your migration

Your engineers or delivery partner keep implementing. Proof works alongside them to recover requirements, investigate compatibility risks and produce evidence within the agreed scope.

  • A versioned behavior agreement.
  • A register of differences and unresolved decisions.
  • Executed checks, results and remaining gaps.
Discuss migration verification

Modernize one component

Scope implementation and verification together. Start with a bounded API or service, an agreed target stack and an owner of the behavior it must preserve.

  • An agreed implementation boundary.
  • The proposed replacement and its verification evidence.
  • Retained requirements and checks for continued development.
Discuss a component migration

Scope, access, price and timing are agreed before work starts. Acceptance of a change and permission to cut over production remain separate decisions.

Before the first conversation

Bring the component and the reason it needs to change.

A short description is enough to start. Tell us the current and target stacks, what you cannot afford to break, and who owns the intended behavior. Do not send credentials or private source code through the form.