Skip to main content

verifiable-gates

PyPI Python Code: Apache-2.0 Rules: CC BY 4.0 DOI 10.5281/zenodo.22103110

Thai: README.th.md.

A registry of CI gates for projects built with or without AI coding agents. Every gate carries evidence of having gone red on a real defect, and the same rules ship as an agent skill, so the agent works under the constraints CI will later enforce. Of the 92 rules, nine have a checker in this package; the other 83 are written for an agent to read.

What this is not. Not a linter and not a SAST replacement. It decides configuration and process posture — pinning, CSP, ADR bookkeeping, supply chain — not code correctness. A green run means the nine checks it can decide found nothing; it says nothing about the 83 it cannot.

Quickstart

pip install verifiable-gates
cd your-project
python -m verifiable_gates.install .     # writes tools/, scaffold.json, gates.yaml, a starting workflow
python3 tools/gates_doctor.py            # runs the checkers the project now holds

What the two commands printed, in an empty git repository (2026-09-05, v0.3.0; the install line's absolute path is shortened to <your-project>):

$ python -m verifiable_gates.install .
installed into <your-project> — 9 gates (9 scan) · check with: python3 tools/gates_doctor.py
for the instruction file your agents read (AGENTS.md, CLAUDE.md), add one line: `run python3 tools/gates_doctor.py --rules before editing`
this bundle also carries the working: 10 practices, each with the lesson behind it and the pull requests it held on — off here; read `python3 tools/gates_doctor.py --working`, turn on with `install <dest> --working`
$ python3 tools/gates_doctor.py
[   NA] actions-sha-pinned — only the bundle's own starting workflow, untouched — nothing of yours to read
[   NA] adr-index-complete — no docs/adr — this rule reads the .md records and the README.md index under docs/adr (scaffold.json adr_path)
[   NA] ci-tools-hash-pinned — only the bundle's own starting workflow, untouched — nothing of yours to read
[   NA] csp-no-inline — no app/templates — this rule reads .html, .htm, .jinja, .jinja2 and .j2 templates under app/templates (scaffold.json templates_path)
[   NA] delete-means-soft-delete — no app — this rule reads Python modules under app (scaffold.json src_path) — session.delete calls outside the purge_paths
[ pass] gates-registry-total
[   NA] image-digest-pinned — no Dockerfile — this rule reads the FROM lines of the root Dockerfile (scaffold.json dockerfiles), and .github/dependabot.yml for a docker ecosystem
[   NA] logic-knows-no-http — no app/services — this rule reads Python modules under app/services (scaffold.json services_path) — their imports, for request-side symbols
[   NA] no-debug-entrypoint — no entrypoint — this rule reads the Python entrypoints run.py, wsgi.py, app.py and main.py (scaffold.json entrypoints), as an AST

waiting on this project's own tests: 0 gates
[exit 0]

On a fresh install the only pass is the registry index, which has to be true about itself. Everything else is NA until the project has something to check. Exit 0 here means nothing was measured, not that the project passed.

Add one workflow with a floating tag and an unpinned install, and the same command answers with two findings and exits 1 (the full transcript, with the third finding the new job itself causes, is in docs/output-semantics.md):

[found] actions-sha-pinned — Every action is pinned to a commit SHA with the version in a comment
  born from: Tags move, commits do not — upload-artifact once sat on @v4 at a single call site for ten days with CI green throughout.
actions-sha-pinned: .github/workflows/lint.yml: actions/checkout@v4
…
[found] ci-tools-hash-pinned — Tools CI installs for itself are pinned by hash, on both the Python and the Node side
  born from: An unpinned install command takes whatever is newest at the second the job runs, and it runs with our workflow's privileges · pinning one package at a time pins only that package while the rest of the tree still floats.
ci-tools-hash-pinned: .github/workflows/lint.yml: pip install ruff
…
** scans found problems in 3 gates: actions-sha-pinned, ci-tools-hash-pinned, gates-registry-total
[exit 1]

Reading the output

Verdict Means Does not mean
pass The checker looked and the rule holds. Any rule without a checker holds.
[found] The checker looked and the rule is broken; the doctor exits 1. The build is unsafe in ways this checker does not read.
NA Nothing of the kind this checker reads is here; it says what it looked for. The rule passed.
[error] The checker could not answer: crash, timeout, undecodable or oversize file, unreadable directory, malformed scaffold.json. Its stderr is passed through; the doctor exits 1. "Looked and found nothing." It is red without a verdict.

Two consequences to know before trusting a green:

  • A project where every rule is NA exits 0. That is an unmeasured project, not a passed one (DECISIONS.md doctor-all-na-exits-zero).
  • A path that scaffold.json names and the project does not have is a finding, not NA: a broken configuration is a defect, not an absence.

The full taxonomy — every NA and [error] case, the --installed record, --rules off an edited bundle, the SARIF mapping — is in docs/output-semantics.md.

Three ways to run it

Entry point Runs Config Default
GitHub Action on push / pull request uses: sayam/verifiable-gates@<commit-sha> # vX.Y.Z · optional with: sarif: gates.sarif (action.yml)
pre-commit before each commit repo: https://github.com/sayam/verifiable-gates · hook gates-doctor, or one hook per rule id (.pre-commit-hooks.yaml)
Claude Code edit hook after every Edit / Write plugin installed (below) · "env": {"VERIFIABLE_GATES_AT_EDIT": "1"} in .claude/settings.json (hooks/hooks.json) off; reports, never refuses

All three run tools/ as the project has it, and none carries a copy of the checkers. Moving the SHA, the rev or the plugin version changes nothing about what the project is held to (DECISIONS.md ci-runs-the-bundle-the-project-installed).

The action is listed on the GitHub Marketplace as verifiable-gates — pin the SHA, not the tag the listing offers. The edit hook hands a finding back to the agent while it still holds the file, and refuses nothing (DECISIONS.md the-edit-hook-reports-and-does-not-refuse).

What the nine checkers decide

Each of the nine stdlib-only checkers is one Python file under tools/checks/, run by the doctor or on its own; the bundle opens no network. python3 tools/gates_doctor.py --rules prints this list off the installed bundle, with each rule's incident.

Rule id What it catches What it reads
gates-registry-total A CI job with no row in the gate index, a row nothing can fail, a test file no gate claims gates.yaml, every workflow under .github/workflows, the test files
actions-sha-pinned A uses: on a floating tag, or on a SHA with no version comment beside it uses: steps of workflows and composite actions under .github
ci-tools-hash-pinned A tool CI installs for itself without hashes or a lock pip, pipx, uv, poetry, pdm, pipenv, npm, npx, yarn, pnpm and python -m build lines in workflows, the scripts they run, the root Dockerfile
image-digest-pinned A base image not pinned to a manifest-index digest, or pinned with nobody to move it FROM lines of the root Dockerfile, .github/dependabot.yml
csp-no-inline Inline script, style or handler in a template .html, .htm, .jinja, .jinja2, .j2 under the templates path
no-debug-entrypoint An entrypoint that can open a debug console run.py, wsgi.py, app.py, main.py, as an AST
logic-knows-no-http A service module importing from the request side Python modules under the services path, their imports
delete-means-soft-delete A session.delete outside the one purge path (layer business) Python modules under the source path
adr-index-complete An ADR missing from the index, a repeated or skipped number, a supersession recorded one way .md records and the README.md index under the ADR path

The paths are the defaults scaffold.json carries; the project moves them there.

The 92 rules

rules.yaml — 92 rules, each carrying the incident that produced it (born_from), because a rule with no origin is a rule nobody knows when to remove. They are rendered into an agent skill in the layout of the Agent Skills specification: skills/verifiable-gates/SKILL.md is the front page, and the full entries sit beside it in references/.

Two pipes this repository does not own install the skill without cloning:

Pipe Command What lands
Skills CLI npx skills add sayam/verifiable-gates lands the four files under skills/verifiable-gates/ and nothing else
Claude Code claude plugin marketplace add sayam/verifiable-gates, then claude plugin install verifiable-gates@verifiable-gates the whole repository, as a plugin

The nine rules with a checker (script: in rules.yaml) are the ones the doctor and the installer decide, and nothing else. The other 83 are the rule sheets an agent is held to by reading, and the Enforced in the reference line on each says how one project turned it into a test. What each pipe sends and fetches, and why there is no registry of this project's own, is in docs/history.md and DECISIONS.md distribution-is-two-pipes-nobody-here-owns.

rules.yaml and the sheets come with the checkout, not with the wheel. The package is the machinery that reads the catalogue:

import verifiable_gates

catalogue = verifiable_gates.rules.load("rules.yaml")
# `package_dir` is where a rule's `script:` is looked for; without it, only the
# shape of the path is checked — a checker that is not there would go unnoticed.
for problem in verifiable_gates.rules.problems(catalogue, package_dir="src/verifiable_gates"):
    print(problem)

Design constraints

The two schemas — rules.py for this catalogue, registry.py for a project's gates.yaml — encode five rules that came from real traps, not from theory:

  • a gate whose layer is internal cannot be portable — a rule tied to one project's architecture, exported as universal, is an overclaim; that hold is in the gate schema, and the rule schema refuses internal outright, since a rule in this catalogue is published whole (portable on a rule is refused as a gate's field);
  • a key neither schema knows is refused, not skipped — a misspelt born_frm is a rule with no origin that looks like one with;
  • anything exported must name the trap that created it (born_from), because a rule with no origin is a rule nobody knows when to remove;
  • proved_by entries must say what they caught and when — a gate nobody has seen go red is indistinguishable from a gate that checks nothing;
  • the vocabularies for kind, severity, layer, and pillar are closed.

A rule and its enforcement live in separate files, because they have separate lifetimes: rules.yaml is what this project publishes; gates.yaml is what this project is itself held to. What was deliberately not done, each with the condition that would expire it, is DECISIONS.md.

Provenance and status

Archived at doi:10.5281/zenodo.22103110, which resolves to the latest version; each release also gets a DOI of its own. The tag evidence-freeze-1 is the state the measurements were taken on and v0.1.0 (2026-08-28) the state the package first shipped in — different commits on purpose. The extraction from the reference implementation, sayam/flask-todolist, is complete; the stage table, the census and what stayed behind are in docs/history.md. Consume a pinned submodule or a versioned dependency, never main.

Licence

  • Code: Apache-2.0. Contributors sign CLA.md — one line in the pull request; you keep your copyright.
  • Rules and documentation: CC BY 4.0.

The application this was extracted from stays AGPL-3.0-or-later. The two differ on purpose: a CI tool is not a network service, and a rule meant to be adopted inside an organisation's internal handbook must not require share-alike.


README.th.md · docs/ · CONTRIBUTING.md · SECURITY.md · CHANGELOG.md · the experiment

Download files

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

Source Distribution

verifiable_gates-0.3.1.tar.gz (469.8 kB view details)

Uploaded Source

Built Distribution

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

verifiable_gates-0.3.1-py3-none-any.whl (274.0 kB view details)

Uploaded Python 3

File details

Details for the file verifiable_gates-0.3.1.tar.gz.

File metadata

  • Download URL: verifiable_gates-0.3.1.tar.gz
  • Upload date:
  • Size: 469.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for verifiable_gates-0.3.1.tar.gz
Algorithm Hash digest
SHA256 480c9c8bc72aca4ad99d8d30cf4c732b026cd174e8e700684401c7ad542dfdec
MD5 211b8dcb8ec454f28b6147a4d4e74815
BLAKE2b-256 570c7462072833bd8d345448e9eb9dd3a53a62b0c3ac5527b128c89f656f46da

See more details on using hashes here.

Provenance

The following attestation bundles were made for verifiable_gates-0.3.1.tar.gz:

Publisher: release.yml on sayam/verifiable-gates

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

File details

Details for the file verifiable_gates-0.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for verifiable_gates-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 98f9aa12f10a8e730700f2c5840b2e3edba4cfbbd3201a367cca5fabc98202ed
MD5 27ca9bef4eda0b637cf5cf27538276f2
BLAKE2b-256 8798bf6b3ef0d7271d91a2ef7bd9298348178425a098b355cfa82253cc935542

See more details on using hashes here.

Provenance

The following attestation bundles were made for verifiable_gates-0.3.1-py3-none-any.whl:

Publisher: release.yml on sayam/verifiable-gates

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

Release history Release notifications | RSS feed

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

This release

0.3.1 This release

2 files

0.3.0

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page