Deterministic verify-gate for AI code changes: apply only if green, else refuse (WRONG-APPLY = 0).
Project description
Greenlock
A deterministic verify-gate for AI-generated code changes. A patch is applied only if the oracle is green — closed-world check passes, the project's own test suite still passes, and no regression is introduced. Otherwise the change is refused, untouched. Headline guarantee: WRONG-APPLY = 0.
Greenlock is not another coding agent. It is the safety layer that sits between any agent (Devin, Copilot, Cursor, a local model — or a human) and your repository. The model proposes; the oracle decides.
Scope of the guarantee: Greenlock defends against accidental breakage and hallucinations from a non-adversarial author. It is not a sandbox and the verifier executes code — run it only in an isolated/ephemeral environment, and read SECURITY.md for the threat model and a malicious-author's limits.
Why
Today's coding agents try to run tests, but still hand you plausible-looking code that may be broken — their guarantee is probabilistic. Review bots (Qodo, CodeRabbit) advise; CI is dumb and reactive; security guardrails check policy, not correctness.
Greenlock gives a refusal contract instead:
| Layer | What it does | What it misses |
|---|---|---|
| CI / branch protection | deterministic, but dumb & reactive | needs tests up front; not in the agent's loop; no grounding |
| Review bots | comment / flag | advisory, probabilistic |
| Security guardrails | block secrets / policy / access | not correctness or regression |
| Greenlock | deterministic merge/reject on correctness | (only as strong as the test suite — see testgen on the roadmap) |
How it works
patch (unified diff) ─► sandbox copy of repo
├─ apply patch (git apply / patch)
├─ closed-world check (no references to symbols that don't exist)
├─ run the project's OWN test suite, independently
└─ compare to baseline → regression?
│
green + no regression ──► ✅ MERGE
anything else ──► 🛑 REJECT (with the failing reason)
Two modes:
- gate-only — verify a diff produced by any source. Model is never called. This is the product: an insurance layer over other agents.
- generate-and-gate — a local writer model proposes a patch and the same oracle loops repair → apply-only-if-green. Cheap, private, on-prem.
Project-agnostic by design: language is auto-detected per file (Python & JavaScript
get deep closed-world grounding; Go/Rust/Java/C/C++/C#/Ruby/PHP via tree-sitter), and the
verifier is auto-detected by manifest (pyproject.toml/setup.py → pytest,
package.json → node, go.mod → go test, Cargo.toml → cargo test). Drop in any repo.
Quickstart
Requirements: Python 3.10+. For the generate mode and the web UI you also need an Ollama endpoint; the gate-only mode needs neither.
pip install greenlock # once published to PyPI → gives the `greenlock` command
# or from source:
git clone https://github.com/abaykopenov/greenlock && cd greenlock
pip install -e . # core (pure stdlib)
pip install -e ".[all]" # + tree-sitter (more languages) + certifi (cloud HTTPS)
After install, run greenlock doctor in your repo to see what it can verify.
Gate a diff (no model needed)
# verdict: MERGE or REJECT, with the reason
greenlock gate path/to/repo my_change.diff # unified CLI (after install)
greenlock check # gate your git changes — no manual diff
greenlock check --staged # only staged changes
greenlock check --against main # changes vs a ref/PR base
greenlock gate path/to/repo my_change.diff --apply # apply the patch iff MERGE
greenlock gate path/to/repo my_change.diff --json
# equivalent module form (no install needed):
python3 -m greenlock.gate path/to/repo my_change.diff
git diff | python3 -m greenlock.gate path/to/repo -
greenlock --version, greenlock init, greenlock harden, and greenlock mcp are the
other subcommands. Run greenlock -h for the full list.
Exit code is 0 on MERGE, 1 on REJECT — drop it straight into a CI step or pre-merge hook.
Run the gate in isolation (untrusted code)
The verifier executes the repo's tests, so for untrusted patches run the whole gate
inside a locked, throwaway Docker container — --network none, read-only rootfs, non-root,
all caps dropped, CPU/mem/PID limits, repo mounted read-only. Even an RCE in a test is
trapped in the container.
docker build -t greenlock:latest . # build the image once
git diff | python3 -m greenlock.gate path/to/repo - --isolated
--isolated is fail-closed: if Docker isn't available it rejects (never silently
downgrades to the unsafe path). Turn it on by default with GREENLOCK_DOCKER=1
(or "docker": "1" in greenlock.json); override the image via --image /
GREENLOCK_DOCKER_IMAGE; force it off for a single run with --no-isolated.
GREENLOCK_DOCKER drives the same strong isolation everywhere (CLI and the MCP
server). A separate, weaker per-command runner (test command in an official language
image) is opt-in via GREENLOCK_VERIFIER_DOCKER — distinct so the two never collide.
Configuration
All settings resolve as env var → greenlock.local.json → default. The OSS default
endpoint is http://localhost:11434; keep your own endpoint/keys in a local file that is
git-ignored:
// greenlock.local.json (git-ignored)
{ "ollama_url": "http://your-ollama:11434", "escalate_model": "gemini-2.5-flash" }
Env equivalents: GREENLOCK_OLLAMA_URL, GREENLOCK_WRITER_MODEL, GREENLOCK_QA_MODEL,
GREENLOCK_EMBED_MODEL, GREENLOCK_ESCALATE, GREENLOCK_GEMINI_KEY. Cloud API keys are
read from GEMINI_API_KEY/GOOGLE_API_KEY or a git-ignored .gemini_key file — never
committed.
Web UI (grounded Q&A + code-edit mode)
python3 webapp/server.py 8012 # → http://127.0.0.1:8012
Harden a repo that has no tests (testgen)
When the gate has nothing to verify against (confidence=degraded), Greenlock can
generate characterization tests (golden-master): the model proposes deterministic
scenarios, the truth is captured by executing the current code (not guessed), and
only stable, green-on-baseline tests are kept. harden_and_verify auto-generates them
for the changed files and re-verifies — so a behavior change is caught even in an
untested repo.
from greenlock.testgen import generate_characterization_tests, harden_and_verify
generate_characterization_tests("path/to/repo", "module.py") # → golden-master tests
harden_and_verify("path/to/repo", open("change.diff").read()) # gate + auto safety net
Pre-commit hook
Block non-green commits locally. Either greenlock init (writes a raw git pre-commit
hook), or via the pre-commit framework — add to your
.pre-commit-config.yaml:
repos:
- repo: https://github.com/abaykopenov/greenlock
rev: v0.1.1
hooks:
- id: greenlock # runs `greenlock check --staged` before each commit
Use it as a CI gate (GitHub Action)
Block any PR that breaks green. In the consuming repo's workflow:
on: pull_request
permissions:
contents: read
pull-requests: write # so Greenlock can post the verdict as a PR comment
jobs:
greenlock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # full history → PR diff
- uses: actions/setup-python@v5
with: { python-version: "3.x" }
- run: pip install -e . # install YOUR test deps (oracle runs your suite)
- uses: abaykopenov/greenlock@v0.1.1 # the gate: merge or block (pin to a release)
The step exits non-zero on reject, so with branch protection the PR can't merge. It
also writes a job summary and (on reject) posts the verdict + reason as a PR comment
(needs pull-requests: write; disable with comment: "false").
Use it from an AI agent (MCP)
Expose the gate as a tool any MCP client (Claude Code, Cursor, …) can call:
pip install "greenlock[mcp]"
greenlock-mcp # stdio MCP server; tools: verify_patch, harden_and_verify, generate_characterization_tests
Benchmarks
A non-circular benchmark (independent test suites as anchors + adversarial tasks proven to break them) measures the headline metric on every run:
python3 -m benchmarks.run_bench pricing # Python
python3 -m benchmarks.run_bench node_pricing # JavaScript
Status & roadmap
Alpha. The gate (closed-world + oracle + regression; gate-only & generate-and-gate),
characterization testgen, the web UI, an MCP server, and a CI Action all work today.
- gate — closed-world + oracle + regression, gate-only & generate-and-gate.
- testgen — characterization tests so untested code gets a safety net.
- delivery rails — MCP tool (
greenlock-mcp) + GitHub Action (action.yml). - nested-package & src-layout support in
testgen. - [~] closed-world for tree-sitter languages — Go & Rust (conservative, zero false positives); other languages stay oracle-only until validated.
- danger-check — rejects patches that introduce
eval/exec/os.system/subprocess/test-environment detection, before the oracle runs (see SECURITY.md).--trust/GREENLOCK_TRUSTmakes it advisory (non-blocking) for trusted authors / self-CI. - execution isolation — opt-in
--isolated(orGREENLOCK_DOCKER=1) runs the whole gate inside a locked Docker container (--network none, read-only rootfs, non-root, dropped caps, CPU/mem/PID limits, repo mounted read-only), fail-closed if Docker is unavailable. The real fix for untrusted-code RCE. - more tree-sitter languages; PyPI release; dashboard; on-prem packaging.
Security
Greenlock executes your test suite to produce a verdict, and its guarantee targets accidental breakage — not a malicious author. See SECURITY.md for the threat model, known attacker capabilities (RCE via tests, closed-world bypass, test-evasion), and mitigations. Run the verifier only in an isolated/ephemeral environment.
License
Project details
Release history Release notifications | RSS feed
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 greenlock-0.1.1.tar.gz.
File metadata
- Download URL: greenlock-0.1.1.tar.gz
- Upload date:
- Size: 116.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bba4201906df4258eada2e817437d7cd7e51fabec3421426965b37c52ca6d7f
|
|
| MD5 |
ca10888aed299125aabf9d524608278a
|
|
| BLAKE2b-256 |
731b18f7c8256fd7acbd8439308cafd5f823afbc215b6b4e20d4920d45ab689a
|
Provenance
The following attestation bundles were made for greenlock-0.1.1.tar.gz:
Publisher:
publish.yml on abaykopenov/greenlock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
greenlock-0.1.1.tar.gz -
Subject digest:
3bba4201906df4258eada2e817437d7cd7e51fabec3421426965b37c52ca6d7f - Sigstore transparency entry: 1871915006
- Sigstore integration time:
-
Permalink:
abaykopenov/greenlock@ec8624936f07f552b123fc5810073c65c8a8f930 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/abaykopenov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ec8624936f07f552b123fc5810073c65c8a8f930 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file greenlock-0.1.1-py3-none-any.whl.
File metadata
- Download URL: greenlock-0.1.1-py3-none-any.whl
- Upload date:
- Size: 124.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6976d3a85cbed6b7ede409e157a8fd72e851020494d8fb697c4031b9ecbf8508
|
|
| MD5 |
54ce6fead1260ed461b5c8106edfde14
|
|
| BLAKE2b-256 |
231a796bdea5563905bc852f3a66f2fc3ffc0d377c45fba178ab3fe1fbd1cab3
|
Provenance
The following attestation bundles were made for greenlock-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on abaykopenov/greenlock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
greenlock-0.1.1-py3-none-any.whl -
Subject digest:
6976d3a85cbed6b7ede409e157a8fd72e851020494d8fb697c4031b9ecbf8508 - Sigstore transparency entry: 1871915111
- Sigstore integration time:
-
Permalink:
abaykopenov/greenlock@ec8624936f07f552b123fc5810073c65c8a8f930 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/abaykopenov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ec8624936f07f552b123fc5810073c65c8a8f930 -
Trigger Event:
workflow_dispatch
-
Statement type: