Formal verification, explained.
Differential verification · Z3 · CBMC · JBMC
Graphify Enterprise ships differential formal verification (internally, verify-edit): for each changed function, it decides whether the new version is observationally equivalent to the old one, or finds a distinguishing input. The old code is the spec; you write nothing. This is the enterprise layer; the open-source core (graph and query) is free and on-device. For the overview, see the formal verification page.
Verdicts
equivalent
01/04
Proved behavior-preserving over all inputs. Sound. (Z3 · CBMC · JBMC)
distinguished
02/04
Behavior changed. Here’s the exact input, and a regression test ready to commit.
may_equivalent
03/04
No divergence found. Empirical, not a proof, and we say so.
unsupported
04/04
We couldn’t reach it. We tell you that, instead of guessing.
- equivalent proves no input distinguishes old and new. Sound (unbounded for Python; bounded for C/C++/Java, see limits). A proof over a domain no input reaches is refused as vacuous.
- distinguished gives a concrete witness input with both the old and new outputs. Sound: solver-proved, or actually executed.
- may_equivalent found no divergence over the exercised inputs. Empirical, not a proof, and the check says so every time.
- unsupported is an honest abstain, with the reason.
An error state exists for infrastructure failures (e.g. a harness that produced no verdict); it is reported, never converted into a verdict. Solver-found counterexamples are replayed in the sandbox first: a witness that does not reproduce at runtime is labeled a likely false alarm and excluded from the behavior-changed count.
The tier ladder
Each tier runs only when everything above it abstained:
- SMT (Z3): sound equivalence or refutation over all inputs, for pure Python functions over int/bool/str/float/lists of scalars.
- Loop-invariant tier: sound
equivalentfor single-loop pure Int/Bool functions via Z3-checked coupling invariants (proof-only, never refutes). - CrossHair (concolic): sound counterexamples by symbolic execution of the real code; inconclusive results fall through.
- Property tier: both versions executed on a deterministic input corpus in a network-denied sandbox; a divergence is a sound refutation, agreement is
may_equivalent. - Trace-carving: captures real receivers and arguments from your own pytest suite and replays them against both versions; this is how methods and framework objects (the dominant abstain cause) get verified.
- Honest abstain.
Tiers 3 to 5 execute code, so they run only when sandbox isolation is actually in force (seatbelt / bubblewrap / docker, all with networking denied). Without isolation they are skipped and the pipeline abstains; it never executes PR code unsandboxed.
Languages
- Python: Z3 SMT and loop-invariant tiers (plus CrossHair counterexamples), then property and trace-carving.
- C / C++: CBMC (bit-precise, bounded), property fallback.
- Java: JBMC (bit-precise, bounded), property fallback.
- Go, JavaScript: property tier only (no sound proof).
- Rust, TypeScript, everything else: not verified today; tracked in the graph, honest abstain.
Running it
CLI
# one function: working tree vs git base; exit 1 iff behavior changed$ graphify verify-edit compute_total --base HEAD~1 --emit-test test_regress.py # opt-in deeper tiers$ graphify verify-edit compute_total --crosshair --carve-tests tests/ # whole change set as a CI gate (advisory unless --block-behavior-change)$ graphify gate --verify-edits --base origin/main --carve --block-behavior-changePrerequisites: a built graph (graphify build), a git checkout, and z3-solver. Optional: cbmc, jbmc + JDK, go, node, crosshair-tool; a missing tool means that tier abstains, and startup validation warns you. graphify verify-plan <fn> prints the blast radius and the callee-first re-verification order for a change.
GitHub App
With the self-hosted app installed, verification runs on every PR by default and posts a graphify verify-edit section in the Graphify check: headline counts, per-function verdicts, evidence tiers for behavior changes (confirmed / hypothesis / likely-false-alarm, with the public caller path), the suggested regression test, and a coverage-gap nudge listing changed functions that are one test away from verifiability. Key knobs:
GRAPHIFY_ENT_VERIFY_EDITS(default on) runs verify-edit on PRs.GRAPHIFY_ENT_BLOCK_BEHAVIOR_CHANGE(default off) makes a behavior-changed edit fail the check. Verification is advisory until you set this.GRAPHIFY_ENT_MAX_VERIFY_FNSandGRAPHIFY_ENT_VERIFY_BUDGET_Scap work per PR (both configurable); any truncation is annotated in the check, never silent.GRAPHIFY_ENT_ISOLATION(auto) selects the sandbox backend;GRAPHIFY_ENT_VERIFY_FORKS(off) keeps fork PR code from ever running unless opted in.
Limits (read this)
- Function-level and differential.It compares one changed function's two versions; cross-function and heap-interaction effects are out of scope. It proves a change is behavior-preserving, not that the code is correct against intent.
- C/C++/Java proofs are bounded(loops are unwound to a configured depth and object/array synthesis is depth-limited): "equivalent" there means within the analyzed bounds; blown bounds downgrade to
may_equivalent, never a silent proof. - Abstains dominate on arbitrary code. On 250 real historical changed functions with no repo context, about 1% got a decisive verdict, and 92% of abstains were input-construction failures (methods needing real objects), not proof-fragment gaps. With the full ladder in-repo (sandbox plus your test suite) we measured roughly 17 to 45% decisive on our own history. The coverage-gap nudge exists because a test that exercises a function is what unlocks its verification.
- may_equivalent is not a proof, and the check says so every time. Solver timeouts and per-PR caps abstain or are annotated, never guessed.
- Nothing leaves your machine: local solvers, network-denied sandboxes, no LLM in the verification path.
This runs in the self-hosted enterprise distribution, in early access. Request early accessand we'll scope the deployment. New to Graphify? Start with the quickstart and the concepts.