Skip to main content

prooft

Proof marks for client builds. A gun barrel gets a proof mark once it survives a test firing. prooft does the same for commits: run your lint and tests locally, stamp the passing commit, and CI and the merge queue skip the jobs that stamp already proves.

$ prooft run lint -- soldr lint          # runs it; on success stamps HEAD
attested lint on 3f2a9c1e0b7d
$ git push                               # CI reads the stamp and skips ci.yml:lint

No server, no signing keys, no artifacts. The proof lives in the commit message as one Ci-Attestation: trailer per gate, the format defined by ci.yml GATE-010. prooft's trailers are byte-compatible with ci-lint's.

The rules

Six rules. Everything else is detail.

  1. A gate is a check on a platform: <ecosystem>/<platform>/<check>, e.g. rust/x86_64-unknown-linux-gnu/clippy. A lane is the local command that proves one or more gates.
  2. A proof is bound to what the lane read. By default that is the exact tree and parents: change one byte, or rebase, and it no longer counts (wrong-tree, wrong-parents). A lane that declares its inputs is bound to those files instead, so it survives a rebase that left them alone. Edit the JSON and the stamp breaks (bad-stamp).
  3. Omission means "not run". A gate without a trailer runs in CI. There is no way to say "skipped".
  4. A job skips only when every gate it covers is proven and fresh (default 24 h per lane), and the head is trusted.
  5. The base decides. CI reads the policy from the PR base. A PR that touches the policy, workflows or ci.toml, a fork, or a title tag ([ci-full]) runs everything remotely.
  6. Releases trust nothing. A release recomputes every gate.

Rule 2 makes forgery visible rather than impossible: a trailer is an honesty contract, not a signature. A forged pass can skip a PR job, but the push to main runs the jobs on the merged commit, so a lie turns into a visible red main, never a silent green.

The merge queue

A queue commit is brand new, so its own trailers can never match. prooft credits the PR's proofs when the queue commit is a single commit on the base and either

  • its tree is identical to the attested PR head (the queue would test the proven bytes), or
  • the lane's declared inputs are unchanged at the queue commit: main moved, but not under anything the lane reads.

Otherwise the queue runs the job. prooft's own main merges through a single-PR merge queue whose verify job runs prooft queue from the base.

The policy: ci-attestations.yml

version: 1
lanes:
  lint:
    max-age-hours: 24                # optional; 24 is the default
    inputs: ["src/**", pyproject.toml] # optional: key the proof to these files
gates:
  python/all/fmt:        {lane: lint}
  python/all/types:      {lane: lint}
  python/linux-x64/test: {lane: tests}
jobs:                                 # <workflow file>:<job id> -> gates
  ci.yml:lint:           [python/all/fmt, python/all/types]
  ci.yml:test-linux-x64: [python/linux-x64/test]

It is a restricted YAML subset (no anchors, aliases, tags or block scalars) parsed by the standard library alone, so every host reads it the same way. fidelity: emulation (Wine, Darling) may prove unit, never test.

Command line

prooft run LANE -- CMD...      run CMD on a clean HEAD; on success stamp LANE's gates
prooft verify [REV]            every gate's state on REV
prooft plan --base REV         which jobs a PR run may skip  (--github-output, --json)
prooft queue --base REV --pr-head REV [--queue-head REV]
prooft lanes                   lanes and the gates each proves
prooft soldr [-- CMD]          soldr lint (porcelain) -> per-target Rust gates
prooft emit [REV] [-o FILE]    REV's valid attestations as a JSON bundle
prooft apply FILE|-            stamp HEAD with the bundle entries valid for HEAD
prooft hook install [--force]  pre-push hook: warn (shadow) or refuse (enforce) an unattested head

run refuses a dirty tree, and refuses if the command changes tracked files or moves HEAD. Re-running a lane replaces only that lane's trailers.

Shadow or enforce

Top-level mode: in the policy decides what the pre-push hook does with an unattested head. shadow (the default) warns and pushes: CI simply runs the jobs. enforce refuses the push. The strictest mode between the pushed commit and the remote tip (or the default branch, for a new branch) wins, so a branch cannot flip itself back to shadow to escape.

Rust: soldr

$ prooft soldr                          # SOLDR_PORCELAIN=1 soldr lint rust --check
attested rust/all/fmt
attested rust/x86_64-unknown-linux-gnu/clippy
attested rust/x86_64-pc-windows-msvc/clippy
attested rust/x86_64-unknown-linux-gnu/dylint

soldr's one-line porcelain report lists its steps; each passed step maps to a gate (fmt -> rust/all/fmt, clippy --target T -> rust/T/clippy, bare clippy/dylint -> the host triple). Only gates the policy declares are stamped. A failing step stamps nothing for itself, so CI runs it.

Python API

import prooft

repo = prooft.open(".")
repo.lane("lint").run(["ruff", "check", "."])  # run a lane, stamp its gates
repo.soldr()  # soldr lint -> per-target Rust gates
repo.verify().ok("python/all/fmt")  # True
plan = repo.plan(base="origin/main", title="Add x")
plan.skipped  # ("ci.yml:lint",)
plan.outputs()  # {"skip_lint": "true", ...}
repo.queue(pr_head="pr", queue_head="HEAD", base="origin/main").skipped

Lower level: prooft.Attestation, prooft.Trailers, prooft.Policy, prooft.verify(commit, policy), prooft.decide(policy, verdict).

Proofs from elsewhere

A build farm that cannot amend your commit can still prove it:

farm$ prooft run tests -- ./ci.py test && prooft emit -o proof.json
you$  prooft apply proof.json            # stamps only entries valid for YOUR HEAD

apply judges the bundle exactly as if its trailers were already on HEAD: same tree and parents, or (for a lane with inputs) unchanged inputs.

Ledgers: hosted proofs by input key

A trailer proves a lane on one commit. A ledger proves it for an input key, wherever that key turns up again: a merge-queue entry, a rebased head, or a PR whose author never ran the lane.

prooft queue --pr-head P --queue-head Q --base B --ledger github \
    --ledger-name 'lane-proof-v1-{lane}-{key}'     # GitHub artifacts, by name
prooft plan --base B --ledger .prooft/ledger      # a directory of JSON files

The rules are the same as for trailers:

  • only lanes with inputs have a key;
  • the entry must be fresh (within the lane's max-age-hours);
  • an untrusted head (fork, title tag, trust-surface edit) skips nothing.

A GitHub artifact also counts only if it is unexpired and was uploaded by the CI workflow (.github/workflows/ci.yml by default) on a pull_request or merge_group run from a branch of this repository. Any other workflow (a claim bot, a dispatch) can upload an artifact with the same name, so it proves nothing.

A repository that keys its lanes its own way passes the keys in: --lane-key rust-test=$(uv run ci/lane_proof.py key --lane rust-test --base B). prooft then judges only trust and freshness.

Simulating GitHub

prooft.sim.github.FakeGitHub is a bare origin plus pull requests, CI runs and a merge queue, all offline. tests/test_simulation.py drives it through every rule above: unattested, partial, stale, tampered, rebased, fork, title tag, policy edit, forged pass, queue credit and queue rerun.

Development

./install      # uv sync + dev tools
./test         # pytest
./lint         # ruff format --check (black style), ruff check (isort, ...), basedpyright
./lint --fix

Status

Alpha (0.2). prooft's own PRs skip fast and test-linux-x64 when their trailers prove them. The merge queue credits every entry of a multi-PR group whose lane inputs are unchanged, and it reads hosted proofs (--ledger) under the same trust rules. The real-world case is obs-rust/obs-studio (docs/CASE-OBS-RUST.md, obs-rust/obs-studio#4460). Next: a ci-lint adapter. See docs/ROADMAP.md.

License: BSD-3-Clause.

Metadata

Release files for prooft 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for prooft 0.2.0
File Size Uploaded Provenance Actions
prooft-0.2.0.tar.gz 31.4 kB Without provenance Details for prooft-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for prooft 0.2.0
File Interpreter ABI Platform Actions
prooft-0.2.0-py3-none-any.whl Python 3 none any Details for prooft-0.2.0-py3-none-any.whl

Total release size: 68.2 kB

Release files / prooft-0.2.0.tar.gz

Download URL prooft-0.2.0.tar.gz
Size 31.4 kB
Tags Source
SHA-256 checksum
How to use checksums
9f79c650b928e6b70caebc97f35149378a29a9c73d4210b71e86cd19d65b0ae8
BLAKE2b-256 checksum
How to use checksums
791d9ed0df1989cdd55aaa68b7a963fd23bbce223d17e7c5c00fc7fa5373cebb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / prooft-0.2.0-py3-none-any.whl

Download URL prooft-0.2.0-py3-none-any.whl
Size 36.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4571ead6be9e2c770c051245cd021cd9738091c65bc158e63bace0c791e1d733
BLAKE2b-256 checksum
How to use checksums
96a20a0fdadb623eff993e09de28f4e066d1b25e4a174aa7f157d3c101fa4bdb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"NixOS","version":"26.05","id":"yarara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for prooft 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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