invariant
https://maximo000.github.io/invariant/
Declare what must stay true. Get more than pass/fail back.
Most CI tells you a build is green. It rarely tells you why it trusts that,
or leaves anything behind for someone else to check without re-running it
themselves. invariant runs a list of named checks against real systems and
writes a proof bundle next to the report — evidence, not just a verdict.
invariants:
- name: no_negative_payments
check: sql
args: {dsn: prod.db, query: "SELECT COUNT(*) FROM payments WHERE amount < 0", must_equal: 0}
- name: latest_backup_restores
check: postgres_restore
args: {dump: backups/latest.dump}
- name: repo_security_controls_fire
check: security_scan
args: {repo: .}
$ invariant run invariant.yaml --evidence proof/
[PASS] no_negative_payments 0.00s 'SELECT COUNT(*)...' = 0
[FAIL] latest_backup_restores 4.31s firedrill restored the archive and found problems
[????] repo_security_controls_fire 0.00s carabiner is not installed (pip install carabiner-sec)
1 passed, 1 failed, 1 unverified
evidence written to proof/
Three statuses, not two
A check that could not run — the tool isn't installed, Docker is down, a
credential is missing — is not the same fact as a check that ran and found a
problem. Folding both into "fail" throws away the difference; folding the
first into "pass" is worse, because now the report is lying. invariant
keeps unverified as its own status everywhere, and it fails the build,
same rule as firedrill and
carabiner: an invariant nobody
could check is not one you get to call satisfied.
Why another one
Because the interesting part isn't the runner — it's what backs each check
type. invariant doesn't reimplement backup verification or security
scanning; it wraps tools that already do those honestly:
postgres_restoreshells out to firedrill, which restores a Postgres dump into a disposable, version-matched container and proves it's usable — rather than trusting that a backup job exiting 0 means anything.security_scanshells out to carabiner, which runs multiple scanners against a repo, ratchets existing findings, and reports only what's new.sqlis the trivial case, built in: one query, one expected scalar. stdlibsqlite3needs nothing extra; apostgres://dsn usespsycopgifpip install invariant[postgres]put it there, and reportsunverifiedrather than crashing if it didn't. Not every invariant needs a whole tool behind it. Seeexamples/recur_invariants.yamlfor two real ones run against recur's actual Postgres schema — "no subscription has a negative amount" and "every logged price change actually changed the price" — neither enforced by a schema constraint, which is the point.
Four repos that individually prove "this backup works" or "this repo is
secure" become one config file that proves all of it, with one report and one
evidence directory, and a fifth check type is one new file in checks/, not
a rewrite.
Install
Not on PyPI yet -- install from a release or straight from git. Once published it'll be pip install invariant-verify (PyPI blocks the plain name "invariant" -- see pyproject.toml -- but the command below and the invariant import stay the same either way):
pip install git+https://github.com/MaXiMo000/invariant
# + `pip install firedrill` / `carabiner-sec`
# for the checks that need them
Use
python examples/make_demo_db.py # or --break negative_payment / orphan_order
invariant run examples/invariant.yaml --evidence proof/
--evidence DIR writes manifest.json (name, status, timing, sha256 per
check) plus one <name>.json per check holding its raw evidence — the exact
command run, stdout/stderr, exit code, and the wrapped tool's own report
where there is one. Enough for someone else to see what actually happened
without taking the exit code's word for it.
Add a check type
A check is a function args: dict -> (status, detail, evidence) where
status is "pass", "fail", or "unverified". Add the module under
invariant/checks/, register it in invariant/checks/__init__.py. That's
the whole extension point — the runner, evidence writer, and CLI don't change.
What's deliberately not built
No plugin SDK, no YAML schema validator, no dashboard, no signing of the
evidence bundle (a sha256 catches an edited file; a real signature is a
separate, later problem). No http, migration_diff, or reconcile check
types yet — they're the natural next ones, added the same way as the three
above, when there's a real invariant to run them against.
MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file invariant_verify-0.1.1.tar.gz.
File metadata
- Download URL: invariant_verify-0.1.1.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21b493b7d6d6b240934ecfef5e9f6e7a0de6a3de831b26140c0636551da26d36
|
|
| MD5 |
3350a292a53d49a5f040a1ce081a45e9
|
|
| BLAKE2b-256 |
d5e9de2ab849ded09ace246daec4884d54efe2eb7fec64377bc063df48959cda
|
Provenance
The following attestation bundles were made for invariant_verify-0.1.1.tar.gz:
Publisher:
release.yml on MaXiMo000/invariant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
invariant_verify-0.1.1.tar.gz -
Subject digest:
21b493b7d6d6b240934ecfef5e9f6e7a0de6a3de831b26140c0636551da26d36 - Sigstore transparency entry: 2756844602
- Sigstore integration time:
-
Permalink:
MaXiMo000/invariant@658c9cd2d40c2381eec33ef846819cfdd73cef03 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MaXiMo000
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@658c9cd2d40c2381eec33ef846819cfdd73cef03 -
Trigger Event:
push
-
Statement type:
File details
Details for the file invariant_verify-0.1.1-py3-none-any.whl.
File metadata
- Download URL: invariant_verify-0.1.1-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9ed0eb3929c167070135205cd0d9a7ecc6c374f63cd0e0c312fbb7569465def
|
|
| MD5 |
3a7ba30ee60eb42f147efd4b8df47e58
|
|
| BLAKE2b-256 |
5502ed79d3e4bab4739f43657f52546488e60e1a81334ee290659caa6c4636c9
|
Provenance
The following attestation bundles were made for invariant_verify-0.1.1-py3-none-any.whl:
Publisher:
release.yml on MaXiMo000/invariant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
invariant_verify-0.1.1-py3-none-any.whl -
Subject digest:
f9ed0eb3929c167070135205cd0d9a7ecc6c374f63cd0e0c312fbb7569465def - Sigstore transparency entry: 2756844659
- Sigstore integration time:
-
Permalink:
MaXiMo000/invariant@658c9cd2d40c2381eec33ef846819cfdd73cef03 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MaXiMo000
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@658c9cd2d40c2381eec33ef846819cfdd73cef03 -
Trigger Event:
push
-
Statement type: