AI agents generate code much faster than humans can verify it.
Can an AI generated implementation be verified automatically?
Engineers already give AI agents specifications: prompts, tickets, acceptance criteria, API contracts.
Usually those specifications are informal and ambiguous.
A formal specification makes intent precise enough to verify automatically.
A Door
Consider a door: it can be open or closed.
If it is closed, it can be opened. And if it is open, it can be closed.
closed --opened--> open
open --closed--> closed
This behavior is the specification.
Implementation
Now consider an implementation with extra steps:
closed --τ--> opening --opened--> open
open --τ--> closing --closed--> closed
The door can be opening and closing.
But the specification only sees open and closed.
We want to verify that the extra states and extra steps preserve the specification.
Labeled Transition Systems
The door behavior can be modeled as a Labeled Transition System(LTS): a graph whose nodes are states and whose edges are labeled transitions.
structure LTS (State : Type u) (Label : Type v) where
Tr : State → Label → State → Prop
The specification has two states: closed and open.
inductive SpecState where
| closed
| open
deriving DecidableEq, Fintype
The implementation has four states: closed, opening, open, and closing.
inductive ImplState where
| closed
| opening
| open
| closing
deriving DecidableEq, Fintype
Both systems use the same visible transitions: opened and closed.
inductive Label where
| opened
| closed
| τ
deriving DecidableEq, Fintype
The implementation also uses τ, a silent internal transition.
The specification and implementation LTSs are constructed this way:
def spec : LTS SpecState Label :=
LTS.mk fun
| .closed, .opened, .open => True
| .open, .closed, .closed => True
| _, _, _ => False
def impl : LTS ImplState Label :=
LTS.mk fun
| .closed, .τ, .opening => True
| .opening, .opened, .open => True
| .open, .τ, .closing => True
| .closing, .closed, .closed => True
| _, _, _ => False
Verify
To prove that the implementation preserves the specification, we relate states from both LTSs.
A related pair means: these two states have the same visible behavior.
The relation must be stable.
If one side performs a visible transition, the other side must be able to perform the same visible
transition, possibly with silent τ transitions before or after it.
After the transition, the resulting states must be related again.
For the door:
implementation: closed --τ--> opening --opened--> open
specification: closed ----------------opened--> open
and:
implementation: open --τ--> closing --closed--> closed
specification: open ----------------closed--> closed
So the extra states are allowed because they do not change the visible behavior.
In Lean:
example : LTS.WeakBisimilarity impl.toLTS spec.toLTS .closed .closed :=
weakBisimilar?_sound impl spec (s₁ := .closed) (s₂ := .closed) (by decide)
You can look at all the code.
Conclusion
Note that this is a proof, not a test.
Tests provide confidence by exploring executions of a program.
Tests are a great tool, particularly property-based and fuzzing tests.
But a proof checks all executions covered by the model.
Formal verification guarantees that the proven properties are correct.
And automatically doing this is very powerful to leverage AI agents code generation.