Proof vs Validation in Software Engineering
The difference between proof and validation is the difference between knowing a system is correct and knowing it works, and this distinction defines the software engineering field.
1. Core Distinction
A proof is a deductive derivation that a proposition is true and follows a chain of logical steps from axioms to conclusion. In a formal proof, every step is governed by a rule of inference. If the axioms are true and the rules are followed, the conclusion is absolutely certain. This is the gold standard of truth in mathematics and logic.
Validation is an empirical check that a system behaves as expected under specified conditions. It relies on observation. You provide an input, observe the output, and compare it to a requirement. Validation does not prove that a system will always work; it only proves that it worked for the specific cases you tested.
In mathematics, we prove theorems, while in software, we validate programs, a contrast that defines the default practice of the field. But the line blurs when we ask: can we prove software correct? This question leads us to formal methods, where we treat code as a mathematical object.
M-1
For some systems, this is possible. Verified compilers like CompCert provide a mathematical proof that the generated executable behaves exactly as prescribed by the source semantics. This means that no matter the input, the compiler will never introduce a bug that was not in the source code. Similarly, the seL4 microkernel uses machine-checked proofs to enforce security boundaries. These proofs ensure that a process cannot access memory it does not own, providing a mathematical guarantee of isolation.
These are the exception, not the default, because formal verification at this scale is expensive. It requires specialized expertise and significant time. For most software, the cost of a proof outweighs the cost of occasional failures.
2. Why Proof Matters
Proof provides three primary benefits: trust, understanding, and reuse.
Trust means absolute confidence in critical systems. In safety-critical software, such as flight control systems or medical devices, a failure can lead to loss of life. In these cases, testing is not enough. Testing only shows that the system works for the cases you thought to test. A proof, however, eliminates the possibility of entire classes of failure. It guarantees that certain bad states can never be reached, regardless of the input.
Understanding reveals why something works, not just that it works. When a developer validates a feature through a test, they know the feature is correct for that test case. But a proof exposes the logical necessity of a result. It maps the path from the requirements to the implementation. This process often reveals hidden assumptions in the design that would have remained invisible during standard testing.
Reuse allows proven components to be composed safely. In traditional software, if you change a low-level library, you must re-test every high-level system that depends on it. This is because you do not know exactly what invariants the library was providing. If a component is proven to maintain a specific invariant, any system that uses it can rely on that invariant as a mathematical fact. You can build a complex system by stacking these proven blocks, knowing that the foundation is solid.
3. Why Validation Matters
Validation is the primary tool of software engineering because it is practical. While formal proof is the gold standard, it is often an impractical goal for most commercial software.
Proof is expensive. It requires a level of mathematical rigor that most developers are not trained for, and the time required to write a proof can be ten times the time required to write the code. Validation is cheap. Most software is delivered through a cycle of testing, observation, and rapid iteration. This allows a team to deliver value quickly and fix bugs as they are discovered.
Validation can cover all inputs through exhaustive testing or fuzzing. While a proof covers the logical space of the program, validation checks the actual execution on real hardware. This is critical because proofs often assume a perfect machine. A proof might show that an algorithm is correct, but it cannot prove that the hardware will not have a bit-flip or a power failure.
Real-world behavior matters more than logical consistency. A system that is logically consistent but fails to meet the user’s needs is not useful. Validation is the process of ensuring that the system solves the right problem. It bridges the gap between the mathematical model of the software and the actual experience of the human user.
4. The Agentic Gap
Large models generate code, but they validate that it works rather than proving it correct, which creates the core tension. When an agent writes a function, it does not use a deductive chain of logic to ensure the code is correct. Instead, it uses a probabilistic model to predict which tokens most likely represent a working solution.
The agent’s reasoning is probabilistic, not deductive, and it predicts the next token based on patterns rather than a chain of logical axioms. This means that an agent can produce code that passes all existing tests but fails on a subtle edge case that was not present in its training data. The agent is not reasoning about the correctness of the code; it is simulating the appearance of correctness.
We cannot write a proof that the agent’s output is correct. We can only validate that it matches the specification. This is why the “human-in-the-loop” is so critical. The human provides the validation that the agent’s output meets the actual intent, while the agent provides the speed of generation. Together, they form a hybrid system where the agent proposes and the human validates.
M-2
5. A Middle Ground
The factory encodes properties as contracts, and it discharges them automatically. This is a step toward proof because the machine verifies what it claims. A contract is a formal statement of a property that must hold true at a specific point in the process. By checking these contracts, the factory moves from simple validation to a form of automated verification.
For example, the contract for the backings stage ensures that every claim is sourced. This is not a probabilistic guess; it is a deterministic check of the artifact on disk. If a claim is missing a source, the contract fails, and the process stops. This prevents the system from advancing to the next stage with an incomplete backing.
M-3
[contracts.backings]
checks = [
{ tool = "fs", op = "exists", path = "backings.md" },
{ tool = "text", op = "sections", path = "backings.md" },
{ tool = "text", op = "sourced", path = "backings.md" },
]The machine then logs the result of these checks, and the state advances only when the checks pass. This creates a verifiable audit trail of the software’s construction. You can look at the logs and see exactly which contracts were discharged and when.
{"attempt":1,"event":"checks","failures":[],"met":true,"stage":"intake","state_now":"topic","ts":"2026-09-20 12:47:12Z"}
{"attempt":1,"event":"checks","failures":[],"met":true,"stage":"topic","state_now":"backing","ts":"2026-09-20 12:48:47Z"}But the agents themselves remain black boxes, so the machine checks the shape of the artifacts instead of the reasoning of the agents that wrote them. This remains the open question: can we eventually verify the reasoning process itself, or will we always be limited to verifying the output?
6. Examples
We can classify claims into three categories: proven, validated, and verified, but the most common are proven and validated. A mathematical result like V = IR is a proof, because it is a deductive result in a formal system and not an empirical check.
Kotlin compiles to Mach-O is a claim validated by building, and we can verify this by inspecting the build artifacts.
M-4
$ cd apps/apple/greetings/greetings/Greetings.framework/Versions/A
$ file Greetings
Greetings: current ar archive random library
$ ar t Greetings
Greetings.framework.o
$ ar x Greetings
$ file Greetings.framework.o
Greetings.framework.o: Mach-O 64-bit arm64 object, flags:<|SUBSECTIONS_VIA_SYMBOLS> __attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Greetings")))
@interface GreetingsGreetings : GreetingsBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (NSString *)greet __attribute__((swift_name("greet()")));
@endIf we try to build a Mach-O binary on a Linux host, the link step is skipped.
> Task :greetings:compileKotlinMacosArm64
> Task :greetings:linkDebugFrameworkMacosArm64 SKIPPED
BUILD SUCCESSFUL in 1s
3 actionable tasks: 2 executed, 1 up-to-dateBut a build on this host validates that Kotlin compiles to machine code.
> Task :greetings:compileKotlinLinuxX64
> Task :greetings:linkDebugSharedLinuxX64 2 warnings generated.
BUILD SUCCESSFUL in 9s
11 actionable tasks: 5 executed, 6 up-to-date
Configuration cache entry reused. $ file greetings/build/bin/linuxX64/debugShared/libgreetings.so
greetings/build/bin/linuxX64/debugShared/libgreetings.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4f13f3e1692e01d1e6f563e62ff2aaadb7071bb7, with debug_info, not strippedThe claim that the agent’s outline is correct is validated by checking it against the material, and the machine verifies this through the topic contract.
{"attempt":1,"event":"checks","failures":[],"met":true,"stage":"topic","state_now":"backing","ts":"2026-09-20 12:48:47Z"}7. Conclusion
Proof and validation are complementary. The factory is a step toward a future where software is both proven and validated.