Skip to main content

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_TRUST makes it advisory (non-blocking) for trusted authors / self-CI.
  • execution isolation — opt-in --isolated (or GREENLOCK_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

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

greenlock-0.1.1.tar.gz (116.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

greenlock-0.1.1-py3-none-any.whl (124.7 kB view details)

Uploaded Python 3

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

Hashes for greenlock-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3bba4201906df4258eada2e817437d7cd7e51fabec3421426965b37c52ca6d7f
MD5 ca10888aed299125aabf9d524608278a
BLAKE2b-256 731b18f7c8256fd7acbd8439308cafd5f823afbc215b6b4e20d4920d45ab689a

See more details on using hashes here.

Provenance

The following attestation bundles were made for greenlock-0.1.1.tar.gz:

Publisher: publish.yml on abaykopenov/greenlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for greenlock-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6976d3a85cbed6b7ede409e157a8fd72e851020494d8fb697c4031b9ecbf8508
MD5 54ce6fead1260ed461b5c8106edfde14
BLAKE2b-256 231a796bdea5563905bc852f3a66f2fc3ffc0d377c45fba178ab3fe1fbd1cab3

See more details on using hashes here.

Provenance

The following attestation bundles were made for greenlock-0.1.1-py3-none-any.whl:

Publisher: publish.yml on abaykopenov/greenlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page