Catalog PyPI
PACKAGE EXPLORERView in registry

acid-judge

PyPI

Fail-closed gate for a locked Python-tool body

ai-agentsexecution-integrityfail-closedsupply-chain
Versions collected1
Direct dependencies4From release 0.2.33
Last observed20 Oct, 18:13 UTC

README

Version 0.2.33 documentation

Acid Judge

Verify what your AI agent actually executes.

Acid Judge verifies that the code approved for an AI agent is the code that actually executes.

Not a policy gate. A policy gate can ALLOW compute_amount after the file on disk has already changed. This gate asks a different question: is this still the approved artifact?

Before execution: the implementation bound for execution matches the approved artifact. The Claude Code hook is this step. Pre ≠ PASS.

After execution: Acid Judge records evidence that the verified implementation ran, and whether the observation conforms to the contract. PASS lives only here.

We don't tell you that your code is safe. We tell you whether it is the code you approved.

An agent can write or change a tool. Acid Judge does not have to believe it. It checks that the approved body is what will run, then checks the observation against the contract. Not enough facts → SKIPPED, not PASS.

Approved-to-executed integrity — Python-first, local, open source, no sandbox, no LLM, deterministic.

Independent execution-integrity layer. Policy asks allowed?. This layer asks which implementation?. A sandbox asks what can the process do?. Embed this component; do not replace those layers.

policy / authorization
        ↓
Acid Judge     ← this layer
        ↓
runtime / sandbox

Python is the first adapter, not the category. Four surfaces: INTEROP.md.

Trust continuity: approved → locked → verified → executed → observed → receipt. Not a checksum feature. The trust layer sits between the agent and the Python tool it is about to run.

PASS     = enough facts to claim the approved body ran
FAIL     = mismatch; the body was not the approved one (or the observation failed)
SKIPPED  = not enough facts. SKIPPED is not PASS.

The lock catches a tool-file swap between lock and judge. It does not catch a shell, and it does not catch files outside runtime_hashes.

Fail-closed gate of a locked Python-tool body. Catches a file swap between lock and judge. Does not catch a shell, does not sandbox the body after PASS, is not a development OS. Not SaaS, not proven_pure.

Package 0.2.33. MIT core. Supervisor binaries: GitHub Releases (linux-x86_64 / windows-x86_64 / macos-arm64), no local cargo. After pip install the binary finds the contour in the installed package, not in cwd.

Not an MCP gateway. Gateways watch poisoned tool descriptions on the network. Acid Judge checks file bytes of a locally approved Python tool (and the judge contour) right before the call. Complementary layer, not a substitute.

Proof: attacks/. Manual copies: ATTACK.md. TCB: TRUST.md. Runtime law: METHOD.md.

60 seconds

./demo.sh

Honest compute_amount is PASS. The swapped body is blocked. No sandbox, no LLM.

Verify it yourself

No signup. No cloud. No trust required.

git clone https://github.com/AleseyRodkin/acid-judge-smoke.git
cd acid-judge-smoke
./smoke.sh

Run the smoke test: acid-judge-smoke. The product repository explains Acid Judge. The smoke repository is the proof.

Install

pip install acid-judge==0.2.33
acid-judge lock --script FILE --out LOCK.json
# commit LOCK.json and locks/index.json
acid-judge locks --index locks/index.json
acid-judge judge --script FILE --plan LOCK.json --input '...'
- uses: AleseyRodkin/acid-engine@v0.2.33
  with:
    index: locks/index.json
    judge: true

Swapped body is not PASS. Proof: acid-judge-smoke.

Hook

pip install acid-judge
export ACID_REPO_ROOT=$PWD
export ACID_LOCKS_INDEX=$PWD/locks/index.json
# copy examples/hooks/pre_tool_use.py; matcher = your ids (YOUR_TOOL_ID)

Pre ≠ PASS. Unknown that reached the hook is deny.

What it protects

  • Tool body swap between lock and judge (if a hook or CI checks the hash)
  • Static local .py imports (dep:). importlib.import_module / exec / eval are not pinned — lock warns; judge FAIL unless the lock has allow_dynamic: true (0.2.32).
  • Judge contour (runtime_hashes)
  • Symlink retarget after lock (bytes of the followed path)
  • Bind-then-disk-write of a lazy local import (sealed against the locked hash, one read)
  • importlib.reload of a sealed helper after seal (sealed bytes, not a later disk write)
  • Two tools that both have helper.py judged concurrently (per-context seal, not a shared sys.modules name)
  • Top-level code in the tool file: CLI, supervisor, hook, locks, and diff compare source_hash before import. Mismatch → the file is not imported.
  • ArtifactRef / JSON blank: source_hash (file bytes) and body_hash (entry AST) are compared to a snapshot before exec. Empty source_hash does not exec. Same function plus extra top-level code is source_hash.
  • PreToolUse hook: a tool that reached the hook and is not in the index is deny. Lookup is exact id or resolved script path, not basename. The settings matcher is how Bash never hits the hook.

Renaming a local variable changes the AST canon — FAIL. Comments and blank lines are not in the canon. The lock is an imprint of a specific toolchain. python_version and canon_kind are checked; a mismatch is FAIL with "re-take the lock", not "the body was swapped". The supervisor checks SHA-256 of the contour (worker.py, python_runtime.py, runner.py, resolve.py, implementation_canon.py, local_deps.py, cli_judge.py, action_driver.py) before identify. No pin → SKIPPED. Mismatch → FAIL. locks --index and CLI judge --plan without a pin → FAIL. judge_script without toolchain → SKIPPED. Incomplete pin → FAIL. judge_script_from_lock reads pins from the lock JSON. cli.py is not in the pin: argparse / help-text patches do not reshoot locks. PASS is decided in cli_judge.py.

What it does not protect

  • Shell outside judge
  • What the body does after PASS (fs / net / process). No isolation.
  • importlib / exec / eval (lock warns; judge FAIL unless allow_dynamic: true. The flag does not pin the loaded target.)
  • site-packages / stdlib supply chain (separate control: SBOM / SLSA)
  • Environment variables (not part of implementation identity)
  • judge_script on an already-imported ScriptModule (library). The CLI, supervisor, and hook hash the file before import. lock loads the file you present — that is how a lock is taken.

Threat model: SECURITY.md. Coverage: attacks/. TCB: TRUST.md.

Three commands

CommandRole
locklock the body
judgebind before run + verdict
receiptjudge … --receipt FILE — Observation + PASS/FAIL/SKIPPED, no proven_pure

locks --index — live body vs lock in git. Does not execute, not hosted. diff --script --plan — approved vs live table. Does not execute, not PASS. receipt --sign / receipt --verify — Ed25519 on the receipt canon, local openssl. Not Sigstore.

The product is Acid Judge. Repository: acid-engine. Import: acid_engine. CLI: acid-judge. PyPI: pip install acid-judge. Do not pip install acid-engine for this product — that name is the archived data-contracts tree (acid-engine==0.2.0). Releases 0.2.21–0.2.22 on that name are yanked.

Showcase:

acid-judge lock --help
acid-judge judge --script examples/bones/n_plus_one.json --plan examples/bones/n_plus_one.plan.json --input '{"n": 3}' --receipt /tmp/bones.receipt.json
acid-judge judge --script examples/bones/n_plus_one.json --input '{"n": 3}'

Why not PASS, short: wrong body / no execution / pure but effects / type mismatch / lock not passed.

License: LICENSE.

Supervisor, not a second canon

The acid-judge binary is a supervisor: identify → bind → run worker → verdict. Only the Python canon hashes the body. Without a worker — SKIPPED, not PASS. Observation without a worker is not a verdict. Contour: cwd/acid_engine/ (this repository), else ACID_ENGINE_ROOT, else the installed package. Not a foreign project's cwd. Linux/Windows/macOS: Releases.

0.2.x compatibility: COMPATIBILITY.md.

Checks

pip install -e ".[dev]"
python -m pytest tests -q
python locks/ci_judge.py
cargo test --locked --manifest-path rust/acid-judge/Cargo.toml

CI: .github/workflows/acid-judge.yml — pytest (3.11/3.12), uses: ./ (python-cli), rust MSRV 1.75 local supervisor on locks/index.json, cargo audit on 1.88. The job fails if a tool is not PASS. Receipt is an artifact. No plan in the index → does not judge.

Dev: pip install -e ".[dev]" — pytest, ruff, mypy.

ruff check acid_engine tests examples
mypy --strict acid_engine locks/ci_judge.py

What holds the gate

  • Hash = declaration + body canon (ast.unparse, else bytecode). ArtifactRef is not identity.
  • plan.lock before run. Mismatch → FAIL, the body does not run.
  • No execution → not PASS. Not enough facts → SKIPPED. bool ≠ int.
  • The worker does not write PASS/FAIL. judge_script without plan+iface → SKIPPED (self-lock is not a verdict). lock --script only writes JSON. judge_script on an already-imported object does not re-check source_hash — the CLI and supervisor do, before import.
  • The acid-judge binary without worker does not judge: SKIPPED.

Five tools: examples/tools/ (clean_text, normalize_id, compute_amount, route_ticket, emit_forecast_card) — in locks/index.json with bones.

One hook: examples/hooks/pre_tool_use.py — Claude Code PreToolUse, bind only. ACID_REPO_ROOT / ACID_LOCKS_INDEX. Matcher: fragment (YOUR_TOOL_ID). Product ids: product fragment. Pre ≠ PASS. No MCP.

Not in 0.2

Sandbox, MCP hook, Sigstore SaaS, hosted registry, markdown specs, WASM, JS bodies, STOL, prices, a second hash canon in Rust.

Downloads & popularity

4downloads
1-day window · source-reported counts19 Oct 2026
7203600
2026-09-16: 720 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-16–2026-09-162026-09-17: 107 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-17–2026-09-172026-09-18: 36 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-18–2026-09-182026-09-19: 615 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-19–2026-09-192026-09-20: 523 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-20–2026-09-202026-09-21: 13 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-21–2026-09-212026-09-22: 73 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-22–2026-09-222026-09-23: 20 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-23–2026-09-232026-09-24: 56 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-24–2026-09-242026-09-25: 13 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-25–2026-09-252026-09-26: 24 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-26–2026-09-262026-09-27: 12 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-27–2026-09-272026-09-28: 18 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-28–2026-09-282026-09-29: 41 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-29–2026-09-292026-09-30: 23 · observed 2026-10-20T18:56:35.2648880Z · period 2026-09-30–2026-09-302026-10-01: 1 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-01–2026-10-012026-10-02: 4 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-02–2026-10-022026-10-03: 3 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-03–2026-10-032026-10-04: 38 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-04–2026-10-042026-10-05: 38 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-05–2026-10-052026-10-06: 7 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-06–2026-10-062026-10-07: 2 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-07–2026-10-072026-10-08: 71 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-08–2026-10-082026-10-09: 18 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-09–2026-10-092026-10-10: 32 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-10–2026-10-102026-10-11: 26 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-11–2026-10-112026-10-12: 5 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-12–2026-10-122026-10-13: 5 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-13–2026-10-132026-10-14: 3 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-14–2026-10-142026-10-15: 13 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-15–2026-10-152026-10-16: 10 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-16–2026-10-162026-10-17: 14 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-17–2026-10-172026-10-18: 44 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-18–2026-10-182026-10-19: 4 · observed 2026-10-20T18:56:35.2648880Z · period 2026-10-19–2026-10-19
16 Sept19 Oct

pypistats · 34 observations shown. Missing days are unknown. Source category: overall: without mirrors.

View observed values
Date / source periodValueObserved at
2026-10-19 – 2026-10-1942026-10-20T18:56:35.2648880Z
2026-10-18 – 2026-10-18442026-10-20T18:56:35.2648880Z
2026-10-17 – 2026-10-17142026-10-20T18:56:35.2648880Z
2026-10-16 – 2026-10-16102026-10-20T18:56:35.2648880Z
2026-10-15 – 2026-10-15132026-10-20T18:56:35.2648880Z
2026-10-14 – 2026-10-1432026-10-20T18:56:35.2648880Z
2026-10-13 – 2026-10-1352026-10-20T18:56:35.2648880Z
2026-10-12 – 2026-10-1252026-10-20T18:56:35.2648880Z
2026-10-11 – 2026-10-11262026-10-20T18:56:35.2648880Z
2026-10-10 – 2026-10-10322026-10-20T18:56:35.2648880Z
2026-10-09 – 2026-10-09182026-10-20T18:56:35.2648880Z
2026-10-08 – 2026-10-08712026-10-20T18:56:35.2648880Z
2026-10-07 – 2026-10-0722026-10-20T18:56:35.2648880Z
2026-10-06 – 2026-10-0672026-10-20T18:56:35.2648880Z
2026-10-05 – 2026-10-05382026-10-20T18:56:35.2648880Z
2026-10-04 – 2026-10-04382026-10-20T18:56:35.2648880Z
2026-10-03 – 2026-10-0332026-10-20T18:56:35.2648880Z
2026-10-02 – 2026-10-0242026-10-20T18:56:35.2648880Z
2026-10-01 – 2026-10-0112026-10-20T18:56:35.2648880Z
2026-09-30 – 2026-09-30232026-10-20T18:56:35.2648880Z
2026-09-29 – 2026-09-29412026-10-20T18:56:35.2648880Z
2026-09-28 – 2026-09-28182026-10-20T18:56:35.2648880Z
2026-09-27 – 2026-09-27122026-10-20T18:56:35.2648880Z
2026-09-26 – 2026-09-26242026-10-20T18:56:35.2648880Z
2026-09-25 – 2026-09-25132026-10-20T18:56:35.2648880Z
2026-09-24 – 2026-09-24562026-10-20T18:56:35.2648880Z
2026-09-23 – 2026-09-23202026-10-20T18:56:35.2648880Z
2026-09-22 – 2026-09-22732026-10-20T18:56:35.2648880Z
2026-09-21 – 2026-09-21132026-10-20T18:56:35.2648880Z
2026-09-20 – 2026-09-205232026-10-20T18:56:35.2648880Z
2026-09-19 – 2026-09-196152026-10-20T18:56:35.2648880Z
2026-09-18 – 2026-09-18362026-10-20T18:56:35.2648880Z
2026-09-17 – 2026-09-171072026-10-20T18:56:35.2648880Z
2026-09-16 – 2026-09-167202026-10-20T18:56:35.2648880Z

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.2.3320 Sept 20264

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
aleseyrodkin/acid-engine

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 18:13 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:2020855700a231d17e05bdcc3ff0474d23f5e2efe9ffd5d99cda6c29b45e82d7