minilaws
Types are statements, programs are proofs, and type checking is proof checking (Curry-Howard). minilaws applies this to AI-edited code, with a dependent-type proof checker in one pure-Python file (stdlib only).
You write ordinary Python, an AI is free to edit it, and the laws in LAWS.laws must keep holding. If an edit breaks a law, your tests fail.
The strategy
It's the approach of OpenAI's Navier-Stokes announcement, scaled down: let the AI do the work, and make checking that work cheap. There, a swarm of agents produced the proof, and a Lean 4 formalization of it was checked in 17 hours. Checking it by hand was estimated at about 132,800 human-hours. Here, the AI writes the code and the proofs, and a small kernel checks them on every test run.
A kernel can't check one thing: whether the statement is the right one. For that, minilaws follows the Lean proof of Fermat's Last Theorem built with Claude. There, the canonical statement was fixed in advance, and Lean's comparator required the proved theorem to be identical to it. Here, a human owns LAWS.laws, and minilaws check --against origin/main requires every law to still mean what it meant on main.
Usage
pip install minilaws
- Ask your AI to write your app's rules in
LAWS.lawsand to prove them. - Run
pytestas usual. Every project with a.lawsfile that pytest collects becomes one test, so a CI that already runs your tests now enforces the laws too. - Protect the laws in review (see Who owns the laws).
If your pytest config narrows collection (testpaths, --ignore) so that a project's .laws files aren't collected, pytest prints minilaws: laws not collected with the project's folder. Widen the collection, or run minilaws check in CI.
You can also check without pytest:
minilaws check # current directory
minilaws check path/to/project
minilaws check --against origin/main # also: the laws are still the ones on main
No configuration is needed. minilaws checks:
- every
*.lawsfile; - every
.pyfile that doesfrom minilaws import Nat.
File order doesn't matter: code is checked first, then laws, then proofs, and within each step a declaration comes after the ones it uses. To pick the files explicitly, add a minilaws.toml with files = [...].
A folder with its own LAWS.laws or minilaws.toml is a separate project: the parent's check skips it, and it gets its own test.
Delete the + 1 in examples/app.py. The code still runs, but it's now wrong:
REJECTED: proofs.laws: proof zero_add: type mismatch in 'cong succ ih'
Who owns the laws
The laws are only worth something if the AI can't change what they say. minilaws closes the routes it can see:
- A file that contains
laws is human-owned. Its laws,defs andinductives may use only the prelude, Python code (.py) and its own declarations. A law can't depend on something defined inproofs.laws, where the AI could change its meaning. This includestheorems in that file, so put helper lemmas in your proofs file. That's a deliberate trade-off (#7). - Python files may only import from
minilaws, and may only call functions defined earlier in the same file, so the code that's checked is the code that runs. - Deleting or renaming
LAWS.lawsleaves its proofs without laws, which fails the check.
The other route is changing the laws themselves: weakening one, or deleting a law together with its proof. Inside one version of the code that's invisible, since the weaker laws really are proved. So minilaws does what Lean's comparator does with a challenge file: it takes the laws from a version the change can't touch and compares.
minilaws check --against origin/main
Every law at origin/main must still exist, with the same statement and the same dependencies, taken transitively. A def or inductive in a laws file must be identical. Code is compared by signature and by the file it comes from, because the code is what's allowed to change. Renaming a binder or editing comments isn't a change, and new laws are fine. Anything else is rejected:
REJECTED: the laws differ from origin/main; a human must approve this:
law add_comm: statement changed
was: (a : Nat) -> (b : Nat) -> Eq Nat (add a b) (add b a)
now: (a : Nat) -> Eq Nat (add a zero) (add zero a)
law add_assoc: removed
A changed statement is printed as the kernel reads it, with the implicit arguments filled in (Eq Nat). That's what the human approves, so a surprise from the elaborator shows up here.
In CI, run it against the PR's base branch, through pytest or the CLI:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: pip install minilaws==0.2.0
- run: pytest --minilaws-against=origin/${{ github.base_ref }} # or: minilaws check --against ...
MINILAWS_AGAINST=origin/main does the same as the pytest option.
Like comparator, the check must run somewhere the change can't reach. Pin the version you install from PyPI, so the checker isn't the one in the PR. Protect the workflow file too: a PR that edits it could drop the step.
When a law change is intentional, this step fails by design, and a human has to approve the change. That human approval is the whole mechanism, as in Lean projects: protect LAWS.laws, minilaws.toml and .github/ with CODEOWNERS plus branch protection, so that approval comes from someone the AI isn't. The discussion is in #5. For example:
LAWS.laws @you
minilaws.toml @you
/.github/ @you
Syntax
def name : T := t -- code
law name : T -- statement (LAWS.laws)
proof name := t -- proof of a law
theorem name : T := t -- helper lemma: statement + proof
inductive List (A : Type 0) : Type 0 where
| nil : List A
| cons : A -> List A -> List A -- generates List.rec (= induction on lists)
{x : A} -> B fun {x} => t -- implicit arguments, inferred by unification
@f A x -- pass implicit arguments explicitly
The prelude defines Nat and Eq with inductive itself, so the kernel has no special cases.
Python subset
from minilaws import Nat
def add(n: Nat, m: Nat) -> Nat:
if m == 0:
return n
return add(n, m - 1) + 1
Supported:
- Functions over
Nat. - Structural recursion
f(..., m - 1, ...)afterif m == 0, with the other arguments unchanged. - Literals,
x + <int>, and calls to functions defined earlier in the same file. - Imports only from
minilaws.
Anything else is refused with unsupported Python (...), so a checked module holds only these functions: keep other code in modules that import them. Checked modules can't call each other, which keeps the trusted translator small (#6). Nat = int, and the laws speak about n >= 0.
Optional: Claude Code plugin
This gives faster feedback during a session: Claude sees a broken law right after the edit, not only when the tests run.
/plugin marketplace add luajapiassu/minilaws
/plugin install minilaws@minilaws
It finds projects like minilaws check does, with no configuration. It blocks edits to every file with a law and to minilaws.toml, re-checks after every edit, and ships a skill with the usual proof patterns. If the hook itself fails (bad config, missing file), it rejects the edit rather than letting it through.
Bash commands are watched by their effect, not their text: the hook records the project files before the command and compares after it. A command that changed a laws file is reported to Claude, and any other change re-checks the proofs. A Bash command can't be blocked before it runs, so the change is already on disk when Claude hears about it. The record lives in the temp folder, so a deliberate command could tamper with it. The real enforcement stays in pytest/CI, where --against catches a changed law however it was edited.
Why the guarantee holds
- Predicative universes (
Type 0 : Type 1), and constructor fields must fit inType 0, so there's noType : Typeparadox. - Inductive types must be strictly positive, and a definition can't call itself. Every term terminates, so there are no looping "proofs".
- An unproved
lawisn't in scope, so it can't be used as a hypothesis. - For proofs, the elaborator (implicit arguments) is not trusted. The kernel (
whnf,conv,infer,check, recursor generation) re-checks its fully explicit output. - For law statements, the elaborator is trusted: the kernel checks that a statement is well-formed, not that it says what you wrote. When a statement changes,
--againstprints the elaborated version for the human who approves it. - The Python translator is trusted, so keep its subset small. A differential test runs translated functions in the kernel and in Python and requires the same results.
Limitations
- Recursors only eliminate into
Type 0, so there is no large elimination: you can't prove that constructors differ (zero ≠ succ n) or other negative statements. - Unary
Nat: a huge literal liken + 5000is rejected as too deep to check. - A checked
.pymodule holds only functions overNat: no classes, constants orif __name__ == "__main__".
Details and possible ways out: #9.
Development
python -m venv .venv && .venv/Scripts/pip install -e . pytest # Windows; use .venv/bin on Unix
.venv/Scripts/python -m pytest
References
- OpenAI's Navier-Stokes proof and its Lean 4 formalization: proof written by AI, checking made cheap by a kernel (17 machine-hours against an estimated 132,800 human-hours). It also names the gap left open: a kernel proves that the proof follows from the statement, not that the statement is the right one. See also the agent swarm behind it. The result itself is disputed.
- Claude's Fermat's Last Theorem Lean proof: closes that gap with a fixed statement,
comparator, an axiom check, and human review of the statement. - Lean's
comparator: checks a solution against a challenge file the author controls (identical statements, including dependencies, plus permitted axioms). It's the model forcheck --against. - SafeVerify: the same check on compiled Lean files, used by the PutnamBench leaderboard.
- FLT proves Mathlib's
FermatLastTheorem, so changing the statement takes a Mathlib PR with other reviewers: the statement lives where the prover can't edit it. - Bend: a language with a
LAWS.bendfile (laws a human writes, proofs the AI must keep valid), the same split asLAWS.lawsand its proofs. Who may editLAWS.bendis a convention there;check --againstis the mechanical part minilaws adds.
License
MIT
Metadata
Release files for minilaws 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| minilaws-0.2.0.tar.gz | 22.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| minilaws-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.8 kB
Release files / minilaws-0.2.0.tar.gz
| Download URL | minilaws-0.2.0.tar.gz |
|---|---|
| Size | 22.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
27ab1ae5740e7066084d4397d47daf70f57658e68675f9bcab0b6ec427d0012a
|
|
BLAKE2b-256 checksum How to use checksums |
bad2c7489c753474206775960d53b6d3b5f8845f1f681246db989fdeb08d0ad3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / minilaws-0.2.0-py3-none-any.whl
| Download URL | minilaws-0.2.0-py3-none-any.whl |
|---|---|
| Size | 22.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f96bab12a1d7d6a086a179222f6bd6dd7e2235c3fb4feaac9efb7720cb1e0a2f
|
|
BLAKE2b-256 checksum How to use checksums |
c32206a32bcf222cecbea95805df0ec7ec32bea51b5c0849816bed56d58ecc6f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logSecurity for minilaws 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for minilaws-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.